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.

Ну есть два моноида для чисел - Product а также SumКак бы вы справились с этим?

Три, а может и больше, функции для одной и той же вещи - нехорошо для красивого языка, который настаивает на абстракции и подобных вещах, таких как Хаскелл

Абстракции не направлены на устранение дублирования кода. Арифметические и моноидные операции - две разные идеи, и, несмотря на то, что они имеют одинаковую семантику, вы ничего не получите, объединив их.

Другие вопросы по тегам