Для конкретизации полученных сведений необходимо рассмотреть что-нибудь более приземленное. Так как списки являются монадами и в то же время списки достаточно скрупулезно изучены, они являются отличным материалом, на котором можно рассмотреть практическое применение механизма монад.
Для списков операция связывания обретает смысл в соединении вместе набора операций, производимых над каждым элементом списка. При использовании со списками сигнатура операции (>>=) приобретает следующий вид:
(>>=) :: [a] -> (a -> [b]) -> [b]
Это обозначает, что дан список значений типа a и функция, которая проецирует значение типа a на список значений типа b. Связывание применяет функцию к каждому элементу данного списка значений типа a и возвращает полученный список значений типа b. Эта операция уже известна — определители списков работают именно таким образом. Т.е. следующие три выражения абсолютно одинаковы:
-- Выражение 1 ----------------------------------------------------------------------------------
[(x, y) | x <- [1, 2, 3], y <- [1, 2, 3], x /= y]
-- Выражение 2-----------------------------------------------------------------------------------
do x <- [1, 2, 3]
y <- [1, 2, 3]
True <- return (x /= y)
return (x, y)
-- Выражение 3-----------------------------------------------------------------------------------
[1, 2, 3] >>= (\x -> [1, 2, 3] >>= (\y -> return (x /= y) >>=
(\r -> case r of
True -> return (x, y)
_ -> fail ””)))
Какое выражение использовать в написании программ — выбирать программисту.
Какое же интуитивное понимание можно вложить в понятие "монада"? Первое, что приходит в голову это то, что монада — это контейнерный тип. Действительно, список — это контейнерный тип, т.к. внутри списка содержатся элементы другого типа. Именно это и показано в определении монадического типа:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
Запись "m a" как бы показывает, что тип a (необходимо чётко помнить, что при определении классов и других типов данных символы типа a, b и т.д. обозначают переменные типов) обрамлён монадическим типом m. Однако в реальности физическое обрамление доступно только для монадического типа "список", т.к. его обозначение в виде квадратных скобок пошло традиционно. В строгой нотации Haskell’а нужно было бы писать что-нибудь вроде: List (1 2 3 4 5) — это список [1, 2, 3, 4, 5].
Какой же практический смысл имеет применение монад в функциональном программировании? Применение монад в функциональных языках — это по существу возвращение к императивности. Ведь операции связывания (>>=) и (>>) предполагают последовательное выполнение связанных выражений с передачей или без результатов вычисления. Т.е. монады — это императивное ядро внутри функциональных языков. С одной стороны это идёт в разрез с теорией функционального программирования, где отрицается понятие императивности, но с другой стороны некоторые задачи решаются только при помощи императивных принципов. И опять же, Haskell предоставляет удивительную возможность по генерации списков, но это только благодаря тому, что сам тип "список" выполнен в виде монады.