Помощь с монадом-читателем

Я новичок в 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.