Ассоциативность

Последний монадический закон говорит, что когда у нас есть цепочка применений монадических функций с помощью операции >>=, не должно иметь значения то, как они вложены. В формальной записи выполнение (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]

ghci> let h = f <=< g

ghci> h 3

[9,-9,6,-6]

Ладно, всё это здорово. Но какое это имеет отношение к закону ассоциативности? Просто, когда мы рассматриваем этот закон как закон композиций, он утверждает, что f <=< (g <=< h) должно быть равнозначно (f <=< g) <=< h. Это всего лишь ещё один способ доказать, что для монад вложенность операций не должна иметь значения.

Если мы преобразуем первые два закона так, чтобы они использовали операцию <=<, то закон левого тождества утверждает, что для каждой монадической функции f выражение f <=< return означает то же самое, что просто вызвать f. Закон правого тождества говорит, что выражение return <=< f также ничем не отличается от простого вызова f. Это подобно тому, как если бы f являлась обычной функцией, и тогда (f . g) . h было бы аналогично f . (g . h), выражение f . id – всегда аналогично f, и выражение id . f тоже ничем не отличалось бы от вызова f.

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