Чтобы уважать быстрых читателей, я сначала начну с точного определения, продолжу с более быстрого «простого английского» объяснения, а затем перейду к примерам.
Вот краткое и точное определение, слегка перефразированное:
Монада (в информатике) формально карта , которая:
отправляет каждый тип X
некоторого заданного языка программирования в новый тип T(X)
(называемый «типом- T
вычислений со значениями в X
»);
оснащен правилом для составления двух функций формы
f:X->T(Y)
и g:Y->T(Z)
функции g∘f:X->T(Z)
;
таким образом, который является ассоциативным в очевидном смысле и унитальным по отношению к определенной вызываемой единичной функции pure_X:X->T(X)
, которую следует рассматривать как принятие значения в чистом вычислении, которое просто возвращает это значение.
Итак, простыми словами, монада - это правило для перехода от любого типа X
к другому типуT(X)
, и правило для перехода от двух функций f:X->T(Y)
и g:Y->T(Z)
(которые вы хотели бы создать, но не можете) к новой функцииh:X->T(Z)
. Что, однако, не является композицией в строгом математическом смысле. Мы в основном «сгибаем» состав функции или переопределяем, как составляются функции.
Кроме того, мы требуем, чтобы правило сочинения монады удовлетворяло «очевидным» математическим аксиомам:
- Ассоциативность : Композиция
f
с, g
а затем с h
(извне) должна быть такой же, как и g
с, h
а затем с f
(изнутри).
- Унитальное свойство :
f
составление с функцией идентичности с обеих сторон должно дать f
.
Опять же, простыми словами, мы не можем просто сойти с ума, переопределив нашу композицию функций так, как нам нравится:
- Сначала нам нужно, чтобы ассоциативность могла составлять несколько функций подряд, например
f(g(h(k(x)))
, и не беспокоиться об указании порядка составления пар функций. Поскольку правило монады только предписывает, как составлять пару функций без этой аксиомы, нам нужно знать, какая пара составлена первой и так далее. (Обратите внимание, что это свойство отличается от того, что свойство commutativity, f
с которым g
были созданы, было таким же, как свойство g
с f
, с которым не требуется).
- И, во-вторых, нам нужно свойство унитала, то есть просто сказать, что идентичности складываются так, как мы их ожидаем. Таким образом, мы можем безопасно реорганизовывать функции всякий раз, когда эти идентификаторы могут быть извлечены.
Итак, еще раз вкратце: монада - это правило расширения типа и составления функций, удовлетворяющих двум аксиомам - ассоциативности и унитальному свойству.
С практической точки зрения, вы хотите, чтобы монада была реализована для вас языком, компилятором или фреймворком, который позаботился бы о создании функций для вас. Таким образом, вы можете сосредоточиться на написании логики своей функции, а не беспокоиться о том, как реализовано их выполнение.
По сути, это в двух словах.
Будучи профессиональным математиком, я предпочитаю избегать называть h
«композицией» f
и g
. Потому что математически это не так. Называя это «композиция» неправильно предполагает, что h
это истинная математическая композиция, а это не так. Это даже не однозначно определяется f
и g
. Вместо этого это результат нового "правила составления" нашей монады. Который может полностью отличаться от фактического математического состава, даже если последний существует!
Чтобы сделать его менее сухим, я попытаюсь проиллюстрировать это на примере, который я аннотирую небольшими разделами, чтобы вы могли перейти непосредственно к делу.
Исключение как примеры монады
Предположим, мы хотим составить две функции:
f: x -> 1 / x
g: y -> 2 * y
Но f(0)
не определено, поэтому выдается исключение e
. Тогда как вы можете определить композиционную ценность g(f(0))
? Брось исключение, конечно же! Может быть, то же самое e
. Возможно новое обновленное исключение e1
.
Что именно здесь происходит? Во-первых, нам нужны новые значения исключений (разные или одинаковые). Вы можете назвать их nothing
или как null
угодно, но суть остается прежней - они должны быть новыми значениями, например, это не должно быть number
в нашем примере здесь. Я предпочитаю не называть их, null
чтобы избежать путаницы с тем, как null
можно реализовать на любом конкретном языке. В равной степени я предпочитаю избегать, nothing
потому что это часто связано с тем null
, что, в принципе, и является тем, что null
следует делать, однако этот принцип часто отклоняется по любым практическим причинам.
Что именно является исключением?
Для любого опытного программиста это тривиальный вопрос, но я бы хотел сказать несколько слов, чтобы погасить червя путаницы:
Исключение - это объект, инкапсулирующий информацию о том, как произошел неверный результат выполнения.
Это может варьироваться от отбрасывания любых деталей и возврата одного глобального значения (например, NaN
или null
) до генерации длинного списка журналов или того, что именно произошло, отправки его в базу данных и репликации по всему распределенному слою хранения данных;)
Важное различие между этими двумя крайними примерами исключения состоит в том, что в первом случае нет побочных эффектов . Во втором есть. Что подводит нас к вопросу (тысяча долларов):
Разрешены ли исключения в чистых функциях?
Краткий ответ : да, но только когда они не приводят к побочным эффектам.
Более длинный ответ. Чтобы быть чистым, вывод вашей функции должен быть однозначно определен ее вводом. Поэтому мы изменяем нашу функцию f
, отправляя 0
новое абстрактное значение, e
которое мы называем исключением. Мы удостоверяемся, что значение не e
содержит никакой внешней информации, которая не определяется однозначно нашим вводом x
. Итак, вот пример исключения без побочных эффектов:
e = {
type: error,
message: 'I got error trying to divide 1 by 0'
}
И вот один с побочным эффектом:
e = {
type: error,
message: 'Our committee to decide what is 1/0 is currently away'
}
На самом деле, это имеет только побочные эффекты, если это сообщение может измениться в будущем. Но если гарантируется, что оно никогда не изменится, это значение станет уникально предсказуемым, и, следовательно, побочный эффект отсутствует.
Чтобы сделать это еще глупее. Функция, возвращающаяся 42
всегда, явно чиста. Но если кто-то сумасшедший решит создать 42
переменную, значение которой может измениться, та же самая функция перестанет быть чистой в новых условиях.
Обратите внимание, что для простоты я использую буквенную нотацию объекта. К сожалению, вещи перепутаны в таких языках, как JavaScript, где error
тип не ведет себя так, как мы хотим здесь, относительно композиции функций, в то время как фактические типы любят null
или NaN
не ведут себя таким образом, а скорее проходят через некоторые искусственные и не всегда интуитивно понятные преобразования типов.
Тип расширения
Поскольку мы хотим изменить сообщение внутри нашего исключения, мы действительно объявляем новый тип E
для всего объекта исключения, а затем это то, что maybe number
делает, кроме его запутанного имени, которое должно быть либо типа, number
либо нового типа исключения E
, так что это действительно союз number | E
из number
и E
. В частности, это зависит от того, как мы хотим построить E
, что не предлагается и не отражено в названии maybe number
.
Что такое функциональная композиция?
Это математические функции операции , принимающие
f: X -> Y
и g: Y -> Z
и построение их состава как функция , h: X -> Z
удовлетворяющая h(x) = g(f(x))
. Проблема с этим определением возникает, когда результат f(x)
не разрешен в качестве аргумента g
.
В математике эти функции не могут быть составлены без дополнительной работы. Строго математическое решение для нашего приведенного выше примера f
и g
заключается в удалении 0
из набора определений f
. С этим новым набором определений (новый, более строгий тип x
) f
становится совместимым с g
.
Однако в программировании не очень практично ограничивать набор таких определений f
. Вместо этого могут быть использованы исключения.
Или как другой подход, искусственные ценности создаются , как NaN
, undefined
, null
, и Infinity
т.д. Таким образом , вы оцениваете 1/0
к Infinity
и 1/-0
к -Infinity
. И затем принудительно верните новое значение в ваше выражение, вместо того, чтобы вызывать исключение. Приводя к результатам вы можете или не можете найти предсказуемые:
1/0 // => Infinity
parseInt(Infinity) // => NaN
NaN < 0 // => false
false + 1 // => 1
И мы вернулись к обычным номерам, готовым двигаться дальше;)
JavaScript позволяет нам продолжать выполнение числовых выражений любой ценой без ошибок, как в примере выше. Это означает, что он также позволяет составлять функции. Именно это и есть монада - это правило составлять функции, удовлетворяющие аксиомам, как определено в начале этого ответа.
Но является ли правило составления функции, возникающей из реализации JavaScript для обработки числовых ошибок, монадой?
Чтобы ответить на этот вопрос, все, что вам нужно, это проверить аксиомы (оставленные здесь как упражнение, но не являющиеся частью вопроса;).
Можно ли использовать исключение для создания монады?
Более того, более полезной монадой было бы правило, предписывающее, что если f
для некоторых выдается исключение, то же самое x
происходит и с любой композицией g
. Плюс сделайте исключение E
глобально уникальным только с одним возможным значением ( конечный объект в теории категорий). Теперь две аксиомы мгновенно проверяются, и мы получаем очень полезную монаду. И результат - то, что известно как возможно монада .