Как и в предыдущей версии, мы берём строку и превращаем её в список слов. Затем производим свёртку, начиная с пустого стека, но вместо выполнения обычной свёртки с помощью функции foldl
используем функцию foldM
. Результатом этой свёртки с помощью функции foldM
должно быть значение типа Maybe
, содержащее список (то есть наш окончательный стек), и в этом списке должно быть только одно значение. Мы используем выражение do
, чтобы взять это значение, и называем его result
. В случае если функция foldM
возвращает значение Nothing
, всё будет равно Nothing
, потому что так устроена монада Maybe
. Обратите внимание на то, что мы производим сопоставление с образцом в выражении do
, поэтому если список содержит более одного значения либо ни одного, сопоставление с образцом окончится неудачно и будет произведено значение Nothing
. В последней строке мы просто вызываем выражение return result
, чтобы представить результат вычисления выражения в обратной польской записи как результат окончательного значения типа Maybe
.
Давайте попробуем:
ghci> solveRPN "1 2 * 4 +"
Just 6.0
ghci> solveRPN "1 2 * 4 + 5 *"
Just 30.0
ghci> solveRPN "1 2 * 4"
Nothing
ghci> solveRPN "1 8 трам-тарарам"
Nothing
Первая неудача возникает из-за того, что окончательный стек не является списком, содержащим один элемент: в выражении do
сопоставление с образцом терпит фиаско. Вторая неудача возникает потому, что функция readMaybe
возвращает значение Nothing
.
Композиция монадических функций
Когда мы говорили о законах монад в главе 13, вы узнали, что функция <=<
очень похожа на композицию, но вместо того чтобы работать с обычными функциями типа a –> b
, она работает с монадическими функциями типа a –> m b
. Вот пример:
ghci> let f = (+1) . (*100)
ghci> f 4
401
ghci> let g = (\x –> return (x+1)) <=< (\x –> return (x*100))
ghci> Just 4 >>= g
Just 401
В данном примере мы сначала произвели композицию двух обычных функций, применили результирующую функцию к 4
, а затем произвели композицию двух монадических функций и передали результирующей функции Just 4
с использованием операции >>=
.
Если у вас есть набор функций в списке, вы можете скомпоновать их все в одну большую функцию, просто используя константную функцию id
в качестве исходного аккумулятора и функцию (.)
в качестве бинарной. Вот пример:
ghci> letf = foldr (.) id [(+1),(*100),(+1)]
ghci> f 1
201
Функция f
принимает число, а затем прибавляет к нему 1
, умножает результат на 100
и прибавляет к этому 1
.
Мы можем компоновать монадические функции так же, но вместо обычной композиции используем операцию <=<,
а вместо id
– функцию return
. Нам не требуется использовать функцию foldM
вместо foldr
или что-то вроде того, потому что функция <=<
гарантирует, что композиция будет происходить монадически.
Когда вы знакомились со списковой монадой в главе 13, мы использовали её, чтобы выяснить, может ли конь пройти из одной позиции на шахматной доске на другую ровно в три хода. Мы создали функцию под названием moveKnight
, которая берёт позицию коня на доске и возвращает все ходы, которые он может сделать в дальнейшем. Затем, чтобы произвести все возможные позиции, в которых он может оказаться после выполнения трёх ходов, мы создали следующую функцию:
in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
И чтобы проверить, может ли конь пройти от start
до end
в три хода, мы сделали следующее:
canReachIn3 :: KnightPos –> KnightPos –> Bool
canReachIn3 start end = end `elem` in3 start
Используя композицию монадических функций, можно создать функцию вроде in3
, только вместо произведения всех позиций, которые может занимать конь после совершения трёх ходов, мы сможем сделать это для произвольного количества ходов. Если вы посмотрите на in3
, то увидите, что мы использовали нашу функцию moveKnight
трижды, причём каждый раз применяли операцию >>=
, чтобы передать ей все возможные предшествующие позиции. А теперь давайте сделаем её более общей. Вот так:
import Data.List
inMany :: Int –> KnightPos –> [KnightPos]
inMany x start = return start >>= foldr (<=<) return (replicate x moveKnight)