Помощь с монадом-читателем
Я новичок в haskell, мне нужно написать контекстно-зависимую программу, поэтому я подумал, что могу использовать Reader Monad для хранения контекста, прочитанного из файла, я знаю, как читать файл, помещающий содержимое в список tuplessomething like [([Char], [Char])], но я не знаю, как реализовать Reader Monad для обеспечения доступности среды для всех компонентов моей программы без использования императивного стиля, в частности, я не знаю, как установить и использовать среду, насколько я понял, я должен указать ее как параметр для всех функций, которые нуждаются в среде с функцией runReader env, но я очень смущен, может кто-нибудь дать мне некоторые указания или хороший руководство? заранее спасибо
Ответы
Ответ 1
Мне кажется, что это проще всего, если вы посмотрите, как решить эту проблему без использования Reader, а затем сравните переведенную версию. Здесь приведен пример сокращения из программы, над которой я работаю, где среда представляет собой набор функций обратного вызова для обновления дисплея. Это немного сложнее, потому что он использует ReaderT вместо Reader, но все работает в основном таким же образом.
runProcess :: Env -> State -> Action -> IO State
runProcess env state action = do
newstate <- processAction state action
let ufunc = mainUFunc env -- get the callback to update the display
ufunc newstate -- update the display
return newstate
Теперь я изменю его, чтобы использовать монаду читателя для прохождения по окружению. Поскольку код уже был в IO, необходимо использовать версию трансформатора монады, ReaderT
.
runProcessR :: State -> Action -> ReaderT Env IO State
runProcessR state action = do
newstate <- lift $ processAction state action
env <- ask -- get the environment from the reader
liftIO $ (mainUFunc env) newstate -- updating is in IO; it needs to be lifted
return newstate
В этот момент основной цикл программы будет по существу:
loop :: State -> ReaderT Env IO ()
loop = do
action <- liftIO getAction
if action == EndLoop
then return ()
else do
st' <- processActionR st action
loop st'
mainLoop :: IO ()
mainLoop = do
env <- setUpCallbacks
let st = initState
runReaderT $ loop st
Итак, как вы можете использовать Reader. Каждой функции, которая использовала параметр среды, больше не требуется. Функции, которые не принимают окружающую среду, могут быть использованы напрямую или отменены, если они являются монадическими.
Ответ 2
Основная схема использования любой "нормальной" монады [0] практически одинакова по всем направлениям. По существу:
- Напишите функции, возвращающие значение монадического типа, используя нотацию
do
, если хотите, так же, как вы напишете функцию IO
, например main
.
- Используйте любые особые функции для монады, с которой вы работаете.
- Вызовите эти функции друг от друга, используя стандартное правило:
- Привязать значение из той же монады, используя
<-
, чтобы получить значение "внутри", заставив другое значение "запуститься".
- Привяжите любое другое значение с помощью
let
, оставив его независимым от монадической структуры.
- Используйте специальную специализированную функцию "запустить", чтобы оценить монадическое вычисление и получить окончательный результат.
Сделайте это, и все беспорядочные детали дополнительной функциональности, описанные монадой (в данном случае, передавая дополнительный параметр среды вокруг), обрабатываются автоматически.
Теперь обычные операции чтения - ask
и local
:
-
ask
является монадическим значением, поддерживающим среду; в блоке do
вы используете его так же, как вы бы использовали что-то вроде getLine
в монаде IO
.
-
local
принимает функцию, которая обеспечивает новую среду и вычисление в монаде Reader, запускает последнюю в среде, модифицированной первой, затем берет результат и помещает его в текущую функцию. Другими словами, он выполняет суб-вычисление с локально измененной средой.
Функция "run" - это творчески названный runReader
, который просто принимает вычисления в монаде Reader и значение среды, запускает первое, используя последнее, и возвращает окончательный результат вне монады.
В качестве примера здесь некоторые функции делают бессмысленный расчет в монаде Reader, где среда является "максимальным значением", которое говорит, когда останавливаться:
import Control.Monad.Reader
computeUpToMax :: (Int -> Int) -> Int -> Reader Int [Maybe Int]
computeUpToMax f x = do
maxVal <- ask
let y = f x
if y > maxVal
then return []
else do zs <- local (subtract y) (computeUpToMax f y)
z <- frob y
return (z:zs)
frob :: Int -> Reader Int (Maybe Int)
frob y = do
maxVal <- ask
let z = maxVal - y
if z == y
then return Nothing
else return $ Just z
Чтобы запустить его, вы должны использовать что-то вроде этого:
> runReader (computeUpToMax (+ 1) 0) 9
[Just 8, Just 6, Nothing]
... где 9
- начальная среда.
Почти такая же структура может быть использована с другими монадами, такими как State
, Maybe
или []
, хотя в последних двух случаях вы обычно использовали только конечное значение монадического результата вместо использования "запустить".
[0]: где нормальное означает не использование магии компилятора, наиболее очевидной "ненормальной" монадой, конечно, является IO
.
Ответ 3
Это лучший ресурс монады IMHO - Все о Monads, а здесь часть для Мода для чтения.