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.