Вот пробный запуск для нескольких монад:
ghci> Just "двигайся дальше" >>= (\x –> return x)
Just "двигайся дальше"
ghci> [1,2,3,4] >>= (\x –> return x)
[1,2,3,4]
ghci> putStrLn "Вах!" >>= (\x –> return x)
Вах!
В этом примере со списком реализация операции >>=
выглядит следующим образом:
xs >>= f = concat (map f xs)
Поэтому когда мы передаём список [1,2,3,4]
функции return
, сначала она отображает [1,2,3,4]
, что в результате даёт список списков [[1],[2],[3],[4]]
. Затем это конкатенируется, и мы получаем наш изначальный список.
Левое тождество и правое тождество являются, по сути, законами, которые описывают, как должна вести себя функция return
. Это важная функция для превращения обычных значений в монадические, и было бы нехорошо, если бы монадическое значение, которое она произвела, имело больше, чем необходимый минимальный контекст.
Ассоциативность
Последний монадический закон говорит, что когда у нас есть цепочка применений монадических функций с помощью операции >>=
, не должно иметь значения то, как они вложены. В формальной записи выполнение (m >>= f) >>= g
– точно то же, что и выполнение m >>= (\x –> f x >>= g)
.
Гм-м, что теперь тут происходит? У нас есть одно монадическое значение, m
, и две монадические функции, f
и g
. Когда мы выполняем выражение (m >>= f) >>= g
, то передаём значение m
в функцию f
, что даёт в результате монадическое значение. Затем мы передаём это новое монадическое значение функции g
. В выражении m >>= (\x –> f x >>= g)
мы берём монадическое значение и передаём его функции, которая передаёт результат применения f x
функции g
. Нелегко увидеть, почему обе эти записи равны, так что давайте взглянем на пример, который делает это равенство немного более очевидным.
Помните нашего канатоходца Пьера, который пытался удержать равновесие, в то время как птицы приземлялись на его балансировочный шест? Чтобы симулировать приземление птиц на балансировочный шест, мы создали цепочку из нескольких функций, которые могли вызывать неуспешное окончание вычислений:
ghci> return (0, 0) >>= landRight 2 >>= landLeft 2 >>= landRight 2
Just (2,4)
Мы начали со значения Just (0, 0)
, а затем связали это значение со следующей монадической функцией landRight 2
. Результатом было другое монадическое значение, связанное со следующей монадической функцией, и т. д. Если бы надлежало явно заключить это в скобки, мы написали бы следующее:
ghci> ((return (0, 0) >>= landRight 2) >>= landLeft 2) >>= landRight 2
Just (2,4)
Но мы также можем записать инструкцию вот так:
return (0, 0) >>= (\x –>
landRight 2 x >>= (\y –>
landLeft 2 y >>= (\z –>
landRight 2 z)))
Вызов return (0, 0)
– то же самое, что Just (0, 0)
, и когда мы передаём это анонимной функции, образец x
принимает значение (0, 0)
. Функция landRight
принимает количество птиц и шест (кортеж, содержащий числа) – и это то, что ей передаётся. В результате мы имеем значение Just (0, 2)
, и, когда передаём его следующей анонимной функции, образец y
становится равен (0, 2)
. Это продолжается до тех пор, пока последнее приземление птицы не вернёт в качестве результата значение Just (2, 4)
, что в действительности является результатом всего выражения.
Поэтому неважно, как у вас вложена передача значений монадическим функциям. Важен их смысл. Давайте рассмотрим ещё один способ реализации этого закона. Предположим, мы производим композицию двух функций, f
и g
:
(.) :: (b –> c) –> (a –> b) –> (a –> c)
f . g = (\x –> f (g x))
Если функция g
имеет тип a –> b
и функция f
имеет тип b –> c
, мы компонуем их в новую функцию типа a –> c
, чтобы её параметр передавался между этими функциями. А что если эти две функции – монадические? Что если возвращаемые ими значения были бы монадическими? Если бы у нас была функция типа a –> m b
, мы не могли бы просто передать её результат функции типа b –> m c
, потому что эта функция принимает обычное значение b
, не монадическое. Чтобы всё-таки достичь нашей цели, можно воспользоваться операцией <=<
:
(<=<) :: (Monad m) => (b –> m c) –> (a –> m b) –> (a –> m c)
f <=< g = (\x –> g x >>= f)
Поэтому теперь мы можем производить композицию двух монадических функций:
ghci> let f x = [x,-x]
ghci> let g x = [x*3,x*2]