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
а как же моноиды? Комитет Хаскелла планирует что-то об этом? Это сломало бы некоторые коды, но я слышал, хотя я не уверен, что есть входящая версия, которая будет иметь большие изменения, что является отличным случаем. Очень жаль... По крайней мере, вилка доступна?
РЕДАКТИРОВАТЬ В ответах, которые я прочитал, есть факт, что для чисел, либо (*)
или же (+)
может вписаться в 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
просто (*)
, В основном я предлагаю рефакторинг кода с "подтягиванием". О, нам нужен новый 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
-...
Ну, я пока не знаю, как это сделать, но думаю, что для всего этого есть отличное решение.
4 ответа
Вы пытаетесь смешать несколько отдельных понятий здесь.
Арифметика и конкатенация списков - очень практичные прямые операции. Если вы напишите:
[1, 2] ++ [3, 4]
... вы знаете, что вы получите [1, 2, 3, 4]
в результате.
Моноид - это математическая алгебраическая концепция, которая находится на более абстрактном уровне. Это означает, что 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, выбрали в качестве реализации по умолчанию, возможно потому, что оно имеет смысл для всех типов элементов списка. И действительно, вы можете использовать альтернативный экземпляр Monoid для списков, заключая их в разные типы; например, существует альтернативный экземпляр Monoid для списков, который выполняет декартово произведение для них.
Понятие "моноид" имеет фиксированное значение и долгую историю в математике, и изменение его определения в Хаскеле означало бы отклонение от математического понятия, чего не должно быть. Monoid - это не просто описание пустого элемента и (буквальная) операция добавления / объединения; это основа для широкого спектра концепций, которые придерживаются интерфейса, который предоставляет Monoid.
Концепция, которую вы ищете, специфична для чисел (потому что вы не можете определить что-то вроде mmultiply
или, может быть mproduce
/ mproduct
для всех случаев Maybe a
например), концепция, которая уже существует и называется полукольцом в математике (ну, вы действительно не охватили ассоциативность в своем вопросе, но вы все равно переходите между различными концепциями в своих примерах - иногда придерживаясь ассоциативности, иногда нет) но общая идея такая же).
В Haskell уже есть реализации Semirings, например, в algebra
пакет.
Однако Monoid, как правило, не является полукольцом, и существует также несколько реализаций полуколец для действительных чисел, в частности, помимо сложения и умножения. Добавление широких обобщенных дополнений в классы очень четко определенных типов, таких как Monoid, не следует делать только потому, что это "будет аккуратно" или "сэкономит несколько нажатий клавиш"; есть причина, почему мы имеем (++)
, (+)
а также mappend
как отдельные понятия, потому что они представляют совершенно разные вычислительные идеи.
При переименовании mappend в (+)
/ (*)
в то время как (+)
а также (*)
оба моноида имеют дополнительные законы распределения, относящиеся к двум операциям, а также законы отмены, например 0*x = 0. По существу, (+)
а также (*)
сформировать кольцо. Два моноида на некотором другом типе могут не удовлетворять этим кольцевым (или даже более слабым полукольцевым) свойствам. Наименование операторов (+)
а также (*)
наводит на мысль об их дополнительных (взаимосвязанных) свойствах. Таким образом, я бы избегал подрывать традиционные математические интуиции, переименовывая mappend
в +
или же *
поскольку эти имена предполагают дополнительные свойства, которые могут не сохраняться. Иногда слишком большая перегрузка (то есть слишком большое обобщение) приводит к потере интуиции и, следовательно, к потере удобства использования.
Если у вас есть два моноида, которые образуют какое-то кольцо, вы можете получить экземпляр Num
из этих, как имена +
" а также " *
предложить дополнительные свойства.
По совмещению (++) и 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.
Если вы посмотрите на Hackage, вы найдете много альтернативных реализаций Prelude, направленных на решение этих проблем.
Ну есть два моноида для чисел - Product
а также Sum
Как бы вы справились с этим?
Три, а может и больше, функции для одной и той же вещи - нехорошо для красивого языка, который настаивает на абстракции и подобных вещах, таких как Хаскелл
Абстракции не направлены на устранение дублирования кода. Арифметические и моноидные операции - две разные идеи, и, несмотря на то, что они имеют одинаковую семантику, вы ничего не получите, объединив их.