Вы видели, как функция pure
реализована для функций, а функция return
– в значительной степени то же самое, что и pure
. Она принимает значение и помещает его в минимальный контекст, который всегда содержит это значение в качестве своего результата. И единственный способ создать функцию, которая всегда возвращает определённое значение в качестве своего результата, – это заставить её совсем игнорировать свой параметр.
Реализация для операции >>=
может выглядеть немного загадочно, но на самом деле она не так уж и сложна. Когда мы используем операцию >>=
для передачи монадического значения функции, результатом всегда будет монадическое значение. Так что в данном случае, когда мы передаём функцию другой функции, результатом тоже будет функция. Вот почему результат начинается с анонимной функции.
Все реализации операции >>=
до сих пор так или иначе отделяли результат от монадического значения, а затем применяли к этому результату функцию f
. То же самое происходит и здесь. Чтобы получить результат из функции, нам необходимо применить её к чему-либо, поэтому мы используем здесь (h w)
, а затем применяем к этому f
. Функция f
возвращает монадическое значение, которое в нашем случае является функцией, поэтому мы применяем её также и к значению w
.
Монада Reader
Если в данный момент вы не понимаете, как работает операция >>=
, не беспокойтесь. Несколько примеров позволят вам убедиться, что это очень простая монада. Вот выражение do
, которое её использует:
import Control.Monad.Instances
addStuff :: Int –> Int
addStuff = do
a <– (*2)
b <– (+10)
return (a+b)
Это то же самое, что и аппликативное выражение, которое мы записали ранее, только теперь оно полагается на то, что функции являются монадами. Выражение do
всегда возвращает монадическое значение, и данное выражение ничем от него не отличается. Результатом этого монадического значения является функция. Она принимает число, затем к этому числу применяется функция (*2)
и результат записывается в образец a
. К тому же самому числу, к которому применялась функция (*2)
, применяется теперь уже функция (+10)
, и результат записывается в образец b
. Функция return
, как и в других монадах, не имеет никакого другого эффекта, кроме создания монадического значения, возвращающего некий результат. Она возвращает значение выражения (a+b)
в качестве результата данной функции. Если мы протестируем её, то получим те же результаты, что и прежде:
ghci> addStuff 3
19
И функция (*2)
, и функция (+10)
применяются в данном случае к числу 3
. Выражение return (a+b)
применяется тоже, но оно игнорирует это значение и всегда возвращает (a+b)
в качестве результата. По этой причине функциональную монаду также называют addStuff
вот так:
addStuff :: Int –> Int
addStuff x = let a = (*2) x
b = (+10) x
in a+b
Вы видите, что монада-читатель позволяет нам обрабатывать функции как значения с контекстом. Мы можем действовать так, как будто уже знаем, что вернут функции. Суть в том, что монада-читатель «склеивает» функции в одну, а затем передаёт параметр этой функции всем тем, которые её составляют. Поэтому если у нас есть множество функций, каждой из которых недостаёт всего лишь одного параметра, и в конечном счёте они будут применены к одному и тому же, то мы можем использовать монаду-читатель, чтобы как бы извлечь их будущие результаты. А реализация операции >>=
позаботится о том, чтобы всё это сработало.
Вкусные вычисления с состоянием
Haskell является чистым языком, и вследствие этого наши программы состоят из функций, которые не могут изменять какое бы то ни было глобальное состояние или переменные – они могут только производить какие-либо вычисления и возвращать результаты. На самом деле данное ограничение упрощает задачу обдумывания наших программ, освобождая нас от необходимости заботиться о том, какое значение имеет каждая переменная в определённый момент времени.
Тем не менее некоторые задачи по своей природе обладают состоянием, поскольку зависят от какого-то состояния, изменяющегося с течением времени. Хотя это не проблема для Haskell, такие вычисления могут быть немного утомительными для моделирования. Вот почему в языке Haskell есть монада State
, благодаря которой решение задач с внутренним состоянием становится сущим пустяком – и в то же время остаётся красивым и чистым.