Haskell > >= операция: почему аргумент функции требуется для возврата другой Monad?
Учитывая выражение:
[0,1..] >>= \i -> [i * 2]
В определении >>=
для List функция лямбда \i -> [i * 2]
отображается по аргументу списка через fmap, что приводит к списку списков [[0], [2]..]
. Поэтому >>=
необходимо сгладить результат, используя функцию соединения, чтобы вернуть список: [0, 2..]
Согласно этот источник: "... определение bind в терминах fmap и join работает для каждой монады m: ma >>= k = join $ fmap k ma
"
Итак, зачем нужно возлагать бремя возврата монады на функцию, поставляемую в → =? Почему бы просто не определить привязку так?
ma >>= k = fmap k ma
Таким образом, вам не нужно иметь дело с выравниванием результата.
Ответы
Ответ 1
То, что вы предлагаете, - это просто определить, что оператор привязки равен fmap
, но с заменой аргументов:
ma >>= k = fmap k ma
-- is equivalent to:
(>>=) = flip fmap
В этом случае, почему бы просто не использовать сам fmap
или его операторную форму <$>
?
(*2) <$> [0,1..]
> [0,2,4,6,...]
Однако это не распространяется на все случаи, когда bind
может использоваться. Разница в том, что монады более мощные, чем функторы. Если функторы позволяют создавать только один вывод для каждого входа, монады позволяют делать все виды сумасшедших вещей.
Рассмотрим, например, следующее:
[0,1,2,3] >>= \i -> [i*2, i*3]
> [0,0,2,3,4,6,6,9]
Здесь функция создает два значения для каждого входа. Это не может быть выражено через fmap
. Для этого требуются join
полученные результирующие значения.
Вот еще один, еще менее очевидный пример:
[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]
Здесь функция либо производит значение, либо нет. Пустой список технически по-прежнему является значением в Монаде списка, но он не может быть получен с помощью fmap
ввода.
Ответ 2
Основной особенностью монады является возможность "сгладить" (join
). Это необходимо для определения хорошей формы композиции.
Рассмотрим композицию двух функций с побочными эффектами, например, в монаде IO
:
foo :: A -> IO B
bar :: B -> IO C
Если >>=
был только fmap
(без последнего join
), композиция
\a -> foo a >>= bar
означало бы
\a -> fmap bar (foo a)
-- i.e.
fmap bar . foo
который выглядит как хорошая композиция, но, к сожалению, имеет тип A -> IO (IO C)
! Если мы не можем сгладить вложенные IO
, каждая композиция добавит еще один слой, и мы получим результат типа формы IO (IO (IO ...))
, показывающий количество композиций.
В лучшем случае это было бы неудобно. В худшем случае это предотвратит рекурсию, такую как
loop = readLn >>= \x -> print x >>= \_ -> loop
так как он приводит к типу с бесконечным вложением IO
.