помогите с монадой читателя - PullRequest
13 голосов
/ 10 августа 2010

Я новичок в haskell, мне нужно написать программу с учетом контекста, поэтому я подумал, что могу использовать Reader Monad для сохранения контекста, читаемого из файла, я знаю, как читать файл, помещая содержимое в список что-то вроде [([Char], [Char])], но я не знаю, как реализовать Reader Monad для обеспечения доступности среды для всех компонентов моей программы без использования императивного стиля, в частности, я не знаю, как чтобы установить и использовать среду, насколько я понял, я должен предоставить ее в качестве параметра для всех функций, которым требуется среда, с помощью функции runReader env, но я очень запутался, кто-нибудь может дать мне некоторые указания или хороший учебник? заранее спасибо

Ответы [ 3 ]

8 голосов
/ 10 августа 2010

Базовая схема использования любой «нормальной» монады [0] почти одинакова для всех. По существу:

  • Напишите функции, которые возвращают значение монадического типа, используя запись do, если хотите, точно так же, как вы бы написали функцию IO, например main.
  • Используйте любые специальные функции для монады, с которой вы работаете.
  • Вызовите эти функции друг от друга, используя стандартное правило:
    • Свяжите значение из той же монады , используя <-, чтобы получить значение «изнутри», в результате чего другое значение будет «выполнено».
    • Свяжите любое другое значение, используя let, оставляя его независимым от монадической структуры.
  • Используйте специализированную функцию run для конкретной монады, чтобы оценить монадическое вычисление и получить окончательный результат.

Сделайте это, и все грязные детали дополнительной функциональности, описанной монадой (в данном случае передача дополнительного параметра среды), обрабатываются автоматически.

Теперь обычные операции чтения: ask и local:

  • ask - монадическое значение, удерживающее окружающую среду; в блоке do вы используете его так же, как если бы вы использовали что-то вроде getLine в монаде IO.
  • local принимает функцию, которая обеспечивает новую среду и вычисления в монаде Reader, запускает последнюю в среде, модифицированной первой, затем берет результат и помещает его в текущую функцию. Другими словами, он выполняет подсчет с локально модифицированным environemnt.

Функцией "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.

5 голосов
/ 10 августа 2010

Я думаю, что проще всего, если вы посмотрите, как решить эту проблему без использования 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

Теперь я изменю его, чтобы использовать монаду Reader для передачи по среде.Поскольку код уже был в 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.Каждой функции, которая раньше принимала параметр среды, больше не нужно.Функции, которые не воспринимают окружение, можно использовать напрямую или отменять, если они монадические.

1 голос
/ 10 августа 2010

Это лучший ресурс для монады - Все о монадах , и вот часть для Читательская монада .

...