Карты операций связывания: ma -> (a -> mb) -> mb
. В ma
и (первом) mb
мы имеем два m
s. Насколько я понимаю, понимание связывания и монадических операций во многом связано с пониманием того, как и как эти два m
(экземпляры монадического контекста) будут объединены. Мне нравится думать о монаде Writer как о примере для понимания join
. Writer может использоваться для регистрации операций. ma
имеет логин. (a -> mb)
создаст еще один журнал для этого первого mb
. Второй mb
объединяет оба этих журнала.
(И плохим примером для подражания является монада Maybe, потому что там Just
+ Just
= Just
и Nothing
+ что-нибудь = Nothing
(или F # Some
и None
) настолько неинформативны, что вы упускаете из виду тот факт, что происходит что-то важное. Вы можете думать, что Just
- это просто одно условие для продолжения, а Nothing
- просто один флаг, который нужно остановить. Как указатели на пути, оставленные позади по мере продолжения вычислений (что является разумным впечатлением, поскольку окончательный Just
или Nothing
создается с нуля на последнем шаге вычисления, и в него ничего не переносится из предыдущих.) Когда действительно нужно сосредоточиться по комбинаторике Just
с и Nothing
с при каждом удобном случае.)
Вопрос выкристаллизовался для меня в чтении Миран Липоваки «Учим тебя гаскеллу для великого блага!», Глава 12, последний раздел о законах монады. http://learnyouahaskell.com/a-fistful-of-monads#monad-laws, Ассоциативность. Это требование: «Выполнение (ma >>= f) >>= g
аналогично выполнению ma >>= (\x -> f x >>= g)
[я использую ma
для m
]». Ну, с обеих сторон аргумент передается сначала f
, а затем g
. Так что же он имеет в виду «не легко увидеть, как эти двое равны» ?? Нелегко увидеть, как они могут отличаться!
Разница заключается в ассоциативности join
звеньев m
с (контекстов) - что делают bind
, наряду с отображением. Bind разворачивается или обходит m
, чтобы получить a
, к которому применяется f
- но это еще не все. Первый m
(на ma
) удерживается, в то время как f
генерирует второй m
(на mb
). Затем bind
объединяет - join
с - оба m
с. Ключ к bind
столько же в join
, сколько и в развернутом (map
). И я думаю, что замешательство из-за join
свидетельствует о том, что необходимо сосредоточиться на аспекте развертывания bind
- получить a
из ma
, чтобы соответствовать сигнатуре аргумента f
- и пропустить тот факт, что два m
с (от ma
, а затем mb
) должны быть согласованы. (Отказ от первого m
может быть подходящим способом справиться с ним в некоторых случаях (возможно) - но это не так в целом - как иллюстрирует Writer.)
Слева мы сначала bind
ma
до f
, затем до g
секунды. Таким образом, журнал будет выглядеть так: ("before f" + "after f") + "after g"
. Справа, пока функции f
и g
применяются в одном и том же порядке, теперь мы привязываем к g
первыми. Таким образом, журнал будет выглядеть так: "before f" + ("after f" + "after g")
. Парень не указан в строке (ах), поэтому логарифм в любом случае одинаков, и закон соблюдается. (Принимая во внимание, что если бы второй журнал вышел как "after f" + "after g" + "before f"
- тогда у нас были бы математические проблемы!).
Пересчитав bind
как fmap
плюс join
для Writer, мы получим fmap f ma
, где f:a -> mb
, в результате m(mb)
. Думайте о первых m
на ma
как о «до f». f
применяется к a
внутри этого первого m
, и теперь прибывает второй m
(или mb
) - внутри первого m
, где происходит сопоставление f
. Думайте о втором m
на mb
как о "после f". m(mb)
= («до f» («после f» b
)). Теперь мы используем Join, чтобы свернуть два журнала, m
s, создав новый m
. Писатель использует моноид и мы соединяем. Другие монады комбинируют контексты другими способами - подчиняясь законам. Что, возможно, является основной частью их понимания.