В то время как монады представлены в Haskell с использованием функций bind и return, они могут также иметь другое представление, используя функцию соединения, такую как обсуждаемая здесь, Я знаю, что тип этой функции M (M (X)) → M (X), но что это на самом деле делает?
Функция объединения Monad
Ответ 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
. Писатель использует моноид, и мы объединяемся. Другие монады объединяют контексты другими способами - подчиняясь законам. Это, возможно, основная часть их понимания.