Чтобы уважать быстрых читателей, я сначала начну с точного определения,
продолжайте с быстрым более «простым английским» объяснением, а затем переходите к примерам.
Вот краткое и точное определение слегка перефразировано:
A монада (в информатике) формально карта, которая:
отправляет каждый тип 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
(с внутри).
- Unital свойство : Компоновка
f
с функцией identity с обеих сторон должна дать f
.
Опять же, простыми словами, мы не можем просто сойти с ума, переопределив нашу композицию функций так, как нам нравится:
- Сначала нам нужно, чтобы ассоциативность могла составлять несколько функций подряд, например,
f(g(h(k(x)))
, и не стоит беспокоиться об указании порядка составления пар функций. Так как правило монады предписывает только, как составлять пару функций , без этой аксиомы, нам нужно знать, какая пара составлена первой и так далее. (Обратите внимание, что он отличается от свойства коммутативности тем, что f
, составленный с g
, был таким же, как g
, составленный с f
, что не требуется).
- И, во-вторых, нам нужно свойство унитала, то есть просто сказать, что тождества складываются так, как мы их ожидаем. Таким образом, мы можем безопасно реорганизовывать функции всякий раз, когда эти идентификаторы могут быть извлечены.
Итак, еще раз вкратце: монада - это правило расширения типа и составления функций, удовлетворяющих двум аксиомам - ассоциативности и унитальному свойству.
В практическом плане вы хотите, чтобы монада была реализована для вас языком, компилятором или фреймворком, который позаботился бы о создании функций для вас. Таким образом, вы можете сосредоточиться на написании логики своей функции, а не беспокоиться о том, как реализовано их выполнение.
По сути, это в двух словах.
Будучи профессиональным математиком, я предпочитаю избегать называть h
«композицией» из f
и g
. Потому что математически это не так. Называя его «композиция» неправильно предполагает, что h
является истинной математической композицией, а это не так. Это даже не однозначно определяется f
и g
. Вместо этого, это результат нового «правила составления» нашей монады. Который может полностью отличаться от фактического математического состава, даже если последний существует!
Монада не функтор ! Функтор F
- это правило перехода от типа X
к типу F(X)
и функции (морфизм) между типами X
и Y
к функциям между F(X)
и F(Y)
(отправка объектов объектам и их морфизмы к морфизмам в теории категорий). Вместо этого монада отправляет пару функций f
и g
новому h
.
Чтобы сделать его менее сухим, позвольте мне проиллюстрировать это на примере.
что я помечаю небольшими разделами, чтобы вы могли пропустить прямо к делу.
Исключение как примеры монад
Предположим, мы хотим составить две функции:
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
глобально уникальным, используя только одно возможное значение ( конечный объект в теории категорий). Теперь две аксиомы мгновенно проверяются, и мы получаем очень полезную монаду. В результате получается так называемая , может быть, монада .