Я знаю, как использовать это, но я не понимаю точно, как это делает (Монада Читателя)
Рассмотрим следующий код (с пропущенными очевидными частями)
main = do
let s = "123456";
let len = runReader calculateContentLength s
putStrLn $ "Original 's' length: " ++ (show len)
calculateContentLength :: Reader String Int
calculateContentLength = do
content <- ask -- this seems to be the same as 'reader id'
return (length content);
Как "ask" попадает в строковый параметр? Насколько я понимаю, из-за объявления типа
calculateContentLength :: Reader String Int
у функции 'convertContentLength' есть тип возвращаемого значения (типа Reader String Int), но у него нет входящих аргументов. Я понимаю, что сама функция - это просто один из двух аргументов, передаваемых в функцию runReader, но как именно второй параметр runReader, 's', привязан к 'ask' внутри 'convertContentLength'?
Другими словами, каким образом "calcContentLength" "знает" (и получает доступ) второй аргумент, передаваемый с помощью "runReader"?
1 ответ
Давайте посмотрим на один способ определить Reader
,
newtype Reader r a = Reader { runReader :: r -> a }
Так Reader
это конструктор, который принимает функцию. Эта функция принимает среду типа r
и возвращает результат типа a
,
ask = Reader { runReader = \env -> env }
ask = Reader id
return
Операция просто игнорирует среду и возвращает значение.
return x = Reader { runReader = \_ -> x }
m >>= n
операция выполняет простую последовательность: она принимает среду, запускает m
в этой среде, а затем работает n
в той же среде, передавая результат m
,
m >>= n = Reader $ \env -> let
a = runReader m env
in runReader (n a) env
Так что теперь мы можем взять ваш пример, десагар и постепенно его уменьшить.
calculateContentLength = do
content <- ask
return (length content)
-- substitute definition of 'ask'
calculateContentLength = do
content <- Reader id
return (length content)
-- substitute definition of 'return'
calculateContentLength = do
content <- Reader id
Reader (\_ -> length content)
-- desugar 'do' into '>>='
calculateContentLength =
Reader id >>= \content -> Reader (\_ -> length content)
-- definition of '>>='
calculateContentLength = Reader $ \env -> let
a = runReader (Reader id) env
in runReader ((\content -> Reader (\_ -> length content)) a) env
-- reduce lambda
calculateContentLength = Reader $ \env -> let
a = runReader (Reader id) env
in runReader (Reader (\_ -> length a)) env
-- definition of 'runReader'
calculateContentLength = Reader $ \env -> let
a = id env
in runReader (Reader (\_ -> length a)) env
-- definition of 'id'
calculateContentLength = Reader $ \env -> let
a = env
in runReader (Reader (\_ -> length a)) env
-- remove redundant variable
calculateContentLength = Reader $ \env
-> runReader (Reader (\_ -> length env)) env
-- definition of 'runReader'
calculateContentLength = Reader $ \env -> (\_ -> length env) env
-- reduce
calculateContentLength = Reader $ \env -> (length env)
calculateContentLength = Reader length
Теперь должно быть легче увидеть, как runReader calculateContentLength
так же, как просто length
, и как ask
не волшебно - монада >>=
Операция строит функцию, которая неявно передает среду для вас, когда вы запускаете вычисления с runReader
,
В действительности, Reader
определяется с точки зрения ReaderT
, который использует монадические действия вместо чистых функций, но форма его реализации по сути та же.