Функция объединения Monad

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

Если M является монадой списка, то M (M (X)) представляет собой список списков, а объединение означает "сгладить". Если M является монадой Maybe, то элемент из M (M (X)) может быть "Just (Just x)", "Nothing Nothing" или "Nothing", а join означает свертывание этих структур логически "Просто х", "Ничто" и "Ничто" соответственно (аналогично ответу на вопрос о том, как присоединиться к объединению в сочетании с побочными эффектами).

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

Но я отвлекаюсь.

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

Ответ 3

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

Ответ 4

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

Как это на самом деле это определяется типом Монады, с которой вы имеете дело. Например, для монады списка "join" эквивалентно concat.

Ответ 5

Карты операции привязки: ma -> (a -> mb) -> mb. В ma и (первом) mb имеем два m s. К моей интуиции понимание взаимосвязи и монадических операций стало ложью, в основном, в понимании того и как эти два m (экземпляры монадического контекста) будут объединены. Мне нравится думать о монаде Писателя как пример понимания join. Writer может использоваться для ведения журналов. ma имеет в нем журнал. (a -> mb) произведет еще один журнал с первым mb. Второй mb объединяет оба этих журнала.

(И плохой пример - это монада Maybe, потому что там Just + Just= Just и Nothing + anything = 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, наряду с отображением. Привязка разворачивается или перемещается по m, чтобы перейти к a, к которому применяется f, но это не все. Первый m (на ma) сохраняется, а f генерирует второй m (на mb). Затем bind объединяет - join s - как m s. Ключ к bind имеет такое же значение в join, что и в развертке (map). И я думаю, что путаница над join указывает на фиксацию на разворачивающемся аспекте bind - получение a из ma для соответствия сигнатуре аргумента f - и игнорирование того факта, что два m (из ma, а затем mb) должны быть согласованы. (Отбрасывание первого m может быть подходящим способом обработки в некоторых случаях (возможно) - но это не так вообще, как иллюстрирует Writer.)

Слева, сначала bind ma до f, затем до g second. Таким образом, журнал будет выглядеть следующим образом: ("before f" + "after f") + "after g". Справа, в то время как функции f и g применяются в том же порядке, теперь мы сначала привязываемся к g. Таким образом, журнал будет выглядеть следующим образом: "before f" + ("after f" + "after g"). Параны не находятся в строке (строках), поэтому журнал один и тот же, и закон соблюдается. (Если второй log вышел как "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. Писатель использует моноид, и мы объединяемся. Другие монады объединяют контексты другими способами - подчиняясь законам. Это, возможно, основная часть их понимания.