Haskell: дублированные функции (+) и (++), mappend
(+)
и (++)
являются только специализациями mappend
; я прав? Зачем они нужны? Это бесполезное дублирование, так как у Haskell есть эти мощные классы и тип вывода.
Пусть, скажем, мы удалим (+)
и (++)
и переименуем mappend
(+)
для удобства просмотра и усиления ввода.
Кодирование было бы более интуитивным, короче и понятным для новичков:
--old and new
1 + 2
--result
3
--old
"Hello" ++ " " ++ "World"
--new
"Hello" + " " + "World"
--result
"Hello World"
--old
Just [1, 2, 3] `mappend` Just [4..6]
--new
Just [1, 2, 3] + Just [4..6]
--result
Just [1, 2, 3, 4, 5, 6]
(Это заставляет меня мечтать.). Три и, возможно, больше, функции для одного и того же, не очень хорошо для красивого языка, который настаивает на абстракции и таких вещах, как Haskell.
Я также видел такие же повторения с монадами: fmap
то же самое или почти, как map
, (.)
, liftM
, mapM
, forM
,...
Я знаю, что существуют исторические причины для fmap
, но как насчет моноидов? Планирует ли комиссия Haskell что-то об этом? Это сломало бы некоторые коды, но я слышал, хотя я не уверен, что есть входящая версия, которая будет иметь большие изменения, что является отличным событием. Это слишком жаль... По крайней мере, вилка доступна?
ИЗМЕНИТЬ
В ответах, которые я читал, есть факт, что для чисел либо (*)
, либо (+)
может поместиться в mappend
. На самом деле, я думаю, что (*)
должен быть частью Monoid
! Посмотрите:
В настоящее время, сбрасывая функции mempty
и mconcat
, мы имеем только mappend
.
class Monoid m where
mappend :: m -> m -> m
Но мы могли бы это сделать:
class Monoid m where
mappend :: m -> m -> m
mmultiply :: m -> m -> m
Это могло бы (возможно, я пока еще не достаточно об этом) ведет себя следующим образом:
3 * 3
mempty + 3 + 3 + 3
0 + 3 + 3 + 3
9
Just 3 * Just 4
Just (3 * 4)
Just (3 + 3 + 3 +3)
Just 12
[1, 2, 3] * [10, 20, 30]
[1 * 10, 2 * 10, 3 * 10, ...]
[10, 20, 30, 20, 40, 60, ...]
Фактически "mmultiply" будет просто определен только в терминах "mappend", поэтому для экземпляров Monoid
нет необходимости переопределять его! Тогда Monoid
ближе к математике; возможно, мы могли бы добавить (-)
и (/)
в класс!
Если это сработает, я думаю, что он разрешит случай Sum
и Product
, а также дублирование функций: mappend
станет (+)
, а новый mmultiply
- просто (*)
.
В основном я предлагаю рефакторинг кода с "pull up".
О, нам понадобится также новый mempty
для (*)
.
Мы могли бы абстрагировать эти операторы в классе MonoidOperator
и определить Monoid
следующим образом:
class (Monoid m) => MonoidOperator mo m where
mempty :: m
mappend :: m -> m -> m
instance MonoidOperator (+) m where
mempty = 0
mappend = --definition of (+)
instance MonoidOperator (*) where
--...
class Monoid m where
-...
Ну, я не знаю, как это сделать, но я думаю, что для всего этого есть классное решение.
Ответы
Ответ 1
Здесь вы пытаетесь смешивать несколько отдельных понятий.
Арифметика и конкатенация списков - очень практичные, прямые операции. Если вы пишете:
[1, 2] ++ [3, 4]
... вы знаете, что в результате вы получите [1, 2, 3, 4]
.
A Monoid - математическое алгебраическое понятие, которое находится на более абстрактном уровне. Это означает, что mappend
не означает, что буквально означает "добавить это к этому"; он может иметь много других значений. Когда вы пишете:
[1, 2] `mappend` [3, 4]
... это некоторые достоверные результаты, которые может произвести эта операция:
[1, 2, 3, 4] -- concatenation, mempty is []
[4, 6] -- vector addition with truncation, mempty is [0,0..]
[3, 6, 4, 8] -- some inner product, mempty is [1]
[3, 4, 6, 8] -- the cartesian product, mempty is [1]
[3, 4, 1, 2] -- flipped concatenation, mempty is []
[] -- treating lists like `Maybe a`, and letting lists that
-- begin with positive numbers be `Just`s and other lists
-- be `Nothing`s, mempty is []
Почему mappend
для списков просто объединяет списки? Потому что это просто определение для моноидов, что ребята, которые написали Haskell Report, выбрали в качестве реализации по умолчанию, вероятно, потому, что это имеет смысл для всех типов элементов списка. И действительно, вы можете использовать альтернативный экземпляр Monoid для списков путем их переноса в различные типы newtypes; существует, например, альтернативный экземпляр Monoid для списков, которые выполняют на них декартовы произведения.
Понятие "моноид" имеет фиксированное значение и долгую историю в математике, и изменение его определения в Haskell означает отклонение от математической концепции, которой не должно быть. Моноид - это не просто описание пустого элемента и операции (добавление/конкатенация); это основа для широкого спектра концепций, которые соответствуют интерфейсу, который предоставляет Monoid.
Концепция, которую вы ищете, специфична для чисел (потому что вы не можете определить что-то вроде mmultiply
или, возможно, mproduce
/mproduct
для всех экземпляров Maybe a
, например), концепция, которая уже существует и называется Semiring в математике (ну, вы действительно не рассматривали ассоциативность в своем вопросе, но вы прыгаете между различными понятиями в ваши примеры в любом случае "иногда придерживаются ассоциативности, иногда нет", но общая идея такая же).
В Haskell уже реализованы реализации Semirings, например, в пакете algebra
.
Однако моноид не является, как правило, Semiring, а также есть несколько реализаций Semirings для действительных чисел, кроме, в частности, добавления и умножения. Добавление широких обобщенных дополнений к очень четким типам классов, таких как Monoid, не должно выполняться только потому, что оно "было бы аккуратным" или "сэкономило бы несколько нажатий клавиш"; есть причина, по которой мы имеем (++)
, (+)
и mappend
как отдельные понятия, потому что они представляют собой совершенно разные вычислительные идеи.
Ответ 2
При переименовании mappend на (+)
/(*)
В то время как (+)
и (*)
являются моноидами, они имеют дополнительные законы распределения, относящиеся к двум операциям, а также законы отмены, например. 0 * x = 0. По существу, (+)
и (*)
образуют ring. Две моноиды на каком-то другом типе могут не удовлетворять этим кольцам (или даже более слабым полукольцевым) свойствам. Именование операторов (+)
и (*)
указывает на их дополнительные (взаимосвязанные) свойства. Таким образом, я бы избегал подрывать традиционные математические интуиции, переименовывая mappend
в +
или *
, поскольку эти имена предполагают дополнительные свойства, которые могут не выполняться. Иногда слишком много перегрузки (т.е. Слишком много обобщений) приводит к потере интуиции и, следовательно, к снижению удобства использования.
Если у вас есть две моноиды, которые образуют какое-то кольцо, вам может понадобиться извлечь из него экземпляр Num
, так как имена "+
" и "*
" предполагают дополнительные свойства.
В conflating (++) и mappend
Переименование mappend
в (++)
может быть более уместным, так как с помощью (++)
существует меньший дополнительный ментальный багаж. Действительно, поскольку списки являются свободным моноидом (то есть моноидом без дополнительных свойств), то использование традиционного оператора списка-конкатенации (++)
для обозначения бинарной операции моноида не кажется страшной идеей.
При определении нескольких моноидов для одного типа
Как вы указываете, обе (+)
являются (*)
являются моноидами, но оба нельзя сделать экземпляром Monoid
для того же типа t
. Одним из решений, которое вы получаете наполовину, является наличие дополнительного параметра типа для класса Monoid
для выделения двух моноидов. Обратите внимание, что классы типов могут быть параметризированы только типами, а не выражением, которое вы укажете в своем вопросе. Подходящим определением будет что-то вроде:
class Monoid m variant where
mappend :: variant -> m -> m -> m
mempty :: variant -> m
data Plus = Plus
data Times = Times
instance Monoid Int Plus where
mappend Plus x y = x `intPlus` y
mempty = 0
instance Monoid Int Times where
mappend Times x y = x `intTimes` y
mempty = 1
(+) = mappend Plus
(*) = mappend Times
Чтобы приложения mappend
/mempty
были разрешены для конкретной операции, каждый должен принять значение, свидетельствующее тип, указывающий конкретный моноидный "вариант".
Кроме того, в названии вашего вопроса упоминается mconcat
. Это совершенно другая операция с mappend
- mconcat
является моноидным гомоморфизмом из свободного моноида в какой-либо другой моноид, т.е. Заменяет cons на mappend и nil на mempty.
Ответ 3
Если вы посмотрите на Hackage, вы найдете много альтернатива Prelude реализация в исправление эти проблемы.
Ответ 4
Ну есть две моноиды для чисел - Product
и Sum
, как бы вы справились с этим?
Три и, возможно, больше, функции для одного и того же, не очень хорошо для красивого языка, который настаивает на абстракции и таких вещах, как Haskell.
Абстракции не касаются устранения дублирования кода. Арифметические и моноидные операции - это две разные идеи, и, несмотря на то, что они имеют одну и ту же семантику, вы ничего не получаете, объединяя их.