Как вы можете видеть, функция join
для списков – это просто concat
. Чтобы разгладить значение монады Writer
, результат которого сам является значением монады Writer
, нам нужно объединить моноидное значение с помощью функции mappend
:
ghci> runWriter $ join (Writer (Writer (1, "aaa"), "bbb"))
(1,"bbbaaa")
Внешнее моноидное значение "bbb"
идёт первым, затем к нему конкатенируется строка "aaa"
. На интуитивном уровне, когда вы хотите проверить результат значения типа Writer
, сначала вам нужно записать его моноидное значение в журнал, и только потом вы можете посмотреть, что находится внутри него.
Разглаживание значений монады Either
очень похоже на разглаживание значений монады Maybe
:
ghci> join (Right (Right 9)) :: Either String Int
Right 9
ghci> join (Right (Left "ошибка")) :: Either String Int
Left "ошибка"
ghci> join (Left "ошибка") :: Either String Int
Left "ошибка"
Если применить функцию join
к вычислению с состоянием, результат которого является вычислением с состоянием, то результатом будет вычисление с состоянием, которое сначала выполняет внешнее вычисление с состоянием, а затем результирующее. Взгляните, как это работает:
ghci> runState (join (state $ \s –> (push 10, 1:2:s))) [0,0,0]
((),[10,1,2,0,0,0])
Здесь анонимная функция принимает состояние, помещает 2
и 1
в стек и представляет push 10
как свой результат. Поэтому когда всё это разглаживается с помощью функции join
, а затем выполняется, всё это выражение сначала помещает значения 2
и 1
в стек, а затем выполняется выражение push 10
, проталкивая число 10
на верхушку.
Реализация для функции join
такова:
join :: (Monad m) => m (m a) –> m a
join mm = do
m <– mm
m
Поскольку результат mm
является монадическим значением, мы берём этот результат, а затем просто помещаем его на его собственную строку, потому что это и есть монадическое значение. Трюк здесь в том, что когда мы вызываем выражение m
<–
mm
, контекст монады, в которой мы находимся, будет обработан. Вот почему, например, значения типа Maybe
дают в результате значения Just
, только если и внешнее, и внутреннее значения являются значениями Just
. Вот как это выглядело бы, если бы значение mm
было заранее установлено в Just (Just 8)
:
joinedMaybes :: Maybe Int
joinedMaybes = do
m <– Just (Just 8)
m
Наверное, самое интересное в функции join
– то, что для любой монады передача монадического значения в функцию с помощью операции >>=
представляет собой то же самое, что и просто отображение значения с помощью этой функции, а затем использование функции join
для разглаживания результирующего вложенного монадического значения! Другими словами, выражение m >>= f
– всегда то же самое, что и join (fmap f m)
. Если вдуматься, это имеет смысл.
При использовании операции >>=
мы постоянно думаем, как передать монадическое значение функции, которая принимает обычное значение, а возвращает монадическое. Если мы просто отобразим монадическое значение с помощью этой функции, то получим монадическое значение внутри монадического значения. Например, скажем, у нас есть Just 9
и функция \x –> Just (x+1)
. Если с помощью этой функции мы отобразим Just 9
, у нас останется Just (Just 10)
.
То, что выражение m >>= f
всегда равно join (fmap f m)
, очень полезно, если мы создаём свой собственный экземпляр класса Monad
для некоего типа. Это связано с тем, что зачастую проще понять, как мы бы разгладили вложенное монадическое значение, чем понять, как реализовать операцию >>=
.
Ещё интересно то, что функция join
не может быть реализована, всего лишь используя функции, предоставляемые функторами и аппликативными функторами. Это приводит нас к заключению, что монады не просто
Функция filter
– это просто хлеб программирования на языке Haskell (при том что функция map
– масло). Она принимает предикат и список, подлежащий фильтрации, а затем возвращает новый список, в котором сохраняются только те элементы, которые удовлетворяют предикату. Её тип таков:
filter :: (a –> Bool) –> [a] –> [a]