Функция соединения монады - PullRequest
26 голосов
/ 01 августа 2010

Хотя монады представлены в Haskell с использованием функций связывания и возврата, они также могут иметь другое представление с использованием функции соединения, например, , обсуждаемое здесь .Я знаю, что тип этой функции - M (M (X)) -> M (X), но что это на самом деле делает?

Ответы [ 5 ]

40 голосов
/ 01 августа 2010

На самом деле, в некотором смысле, join - это то, где происходит вся магия - (>>=) используется в основном для удобства.

Все классы типов на основе Functor описывают дополнительную структуру, используя некоторый тип. В Functor эта дополнительная структура часто рассматривается как «контейнер», в то время как в Monad она обычно рассматривается как «побочные эффекты», но это всего лишь (иногда вводящие в заблуждение) сокращения - это тоже самое кстати и не особо ничего особенного [0] .

Отличительной особенностью Monad по сравнению с другими Functor является то, что он может встраивать поток управления в дополнительную структуру. Причина, по которой он может это сделать, состоит в том, что, в отличие от fmap, который применяет одну плоскую функцию ко всей структуре, (>>=) проверяет отдельные элементы и строит из этого новую структуру.

С простым Functor построение новой структуры из каждого фрагмента исходной структуры вместо этого вложит Functor, где каждый слой представляет точку потока управления. Это, очевидно, ограничивает полезность, поскольку результат является грязным и имеет тип, который отражает структуру используемого управления потоком.

Монадические "побочные эффекты" - это структуры, которые имеют несколько дополнительных свойств [1] :

  • Два побочных эффекта могут быть сгруппированы в один (например, "do X" и "do Y" становятся "do X, затем Y"), и порядок группировки не имеет значения, пока порядок эффектов поддерживается.
  • Существует побочный эффект "ничего не делать" (например, группировка "делать X" и "ничего не делать" такая же, как просто "делать X")

Функция join - это не что иное, как операция группировки: вложенный тип монады, такой как m (m a), описывает два побочных эффекта и порядок их возникновения, а join группирует их вместе в один побочный эффект.

Таким образом, что касается монадических побочных эффектов, операция связывания является сокращением для «принять значение со связанными побочными эффектами и функцией, которая вводит новые побочные эффекты, а затем применить функцию к значению, комбинируя побочные эффекты. за каждый ".

[0]: За исключением IO. IO является очень особенным.

[1]: Если вы сравните эти свойства с правилами для экземпляра Monoid, вы увидите близкие параллели между ними - это не совпадение, и фактически это то, что что "просто моноид в категории эндофункторов, в чем проблема?" линия относится к.

27 голосов
/ 01 августа 2010

То, что присоединение делает , было адекватно описано другими ответами, я думаю. Если вы ищете более интуитивное понимание ... если вам интересно, что означает "соединение" ... то, к сожалению, ответ будет отличаться в зависимости от рассматриваемой монады, в частности от того, что означает M (X) " "и что означает M (M (X))".

Если M - это монада Список, то M (M (X)) - это список списков, а объединение означает «сплющить». Если M - это монада Maybe, то элементом M (M (X)) может быть «Just (Just x)», «Just Nothing» или «Nothing», и объединение означает разрушение этих структур логическим способом «Just x», «Nothing» и «Nothing» соответственно (аналогично ответу Camccann на объединение, сочетающему побочные эффекты).

Для более сложных монад M (M (X)) становится очень абстрактной вещью, и решение того, что M (M (X)) и объединение «означает», становится более сложным. В любом случае это похоже на случай с монадой List, когда вы объединяете два слоя абстракции монады в один слой, но смысл будет меняться. Для Государственной монады ответ Camccann на объединение двух побочных эффектов - это удар: объединение, по сути, означает объединение двух последовательных переходов состояний. Монада Continuation особенно разрушительна для мозга, но математическое объединение здесь на самом деле довольно аккуратно: M (X) соответствует «двойному двойному пространству» X, которое математики могут записать как X** (сами продолжения, то есть карты из X- > R, где R - набор конечных результатов, соответствует единственному двойственному пространству X*), а соединение соответствует чрезвычайно естественной карте от X**** до X**. Тот факт, что монады продолжения удовлетворяют законам монад, соответствует математическому факту, что, как правило, нет особого смысла применять оператор двойного пространства * более чем в два раза.

Но я отвлекся.

Лично я стараюсь сопротивляться желанию применить единую аналогию ко всем возможным типам монад; Монады - это слишком общее понятие, чтобы их можно было описать одной описательной аналогией. Что означает объединение, будет зависеть от того, с какой аналогией вы работаете в данный момент времени.

7 голосов
/ 01 августа 2010

На той же странице мы получаем эту информацию join x = x >>= id, зная, как работают функции bind и id, вы сможете понять, что делает join.

5 голосов
/ 01 августа 2010

То, что он делает, концептуально, можно определить, просто посмотрев на тип: он разворачивает или выравнивает внешний монадический контейнер / вычисление и возвращает произведенное в нем монадическое значение (я).это определяется тем типом монады, с которым вы имеете дело.Например, для монады List «join» эквивалентен concat .

1 голос
/ 26 апреля 2017

Карты операций связывания: 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. Писатель использует моноид и мы соединяем. Другие монады комбинируют контексты другими способами - подчиняясь законам. Что, возможно, является основной частью их понимания.

...