2010-08-10 3 views
13

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

+0

Вы уверены, что вам нужен «Читатель» в первую очередь? «Сделать среду доступной для всех компонентов», как правило, это не лучший способ написать код в Haskell. Можете ли вы описать задачу, над которой работаете, более подробно? –

+3

@Travis Brown: Это может иметь смысл, если у вас есть большой кусок по существу статических данных, необходимый как есть во многих местах по всей программе, который доступен только во время выполнения, например, путем загрузки файлов данных. Представьте себе программу, в которой весь текст локализуется и загружается из файла ресурсов при запуске программы, например. –

+0

На самом деле, если что-то звучит для меня сомнительно, это тип '[[[Char], [Char])]. Зная, что это среда, это звучит подозрительно, как строковый словарь, который должен быть * наименее * быть 'Data.Map.Map String String' вместо этого, если не что-то еще более очаровательное, как прекрасное [bytestring trie] (http: //hackage.haskell.org/package/bytestring-trie). –

ответ

5

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

8

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

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

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

Теперь обычные операции считывателей ask и local:

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

Функция «запуск» является творчески названием runReader, который просто берет вычисление в считывающей монаде и значении окружающей среды, работает бывшие с использованием последнего, и возвращает конечный результат вне монады.

В качестве примера, вот некоторые функции делают некоторые бессмысленные вычисления в Читателя монады, где окружающая среда является «максимальное значение», что говорит, когда остановиться:

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 или [], хотя в последних двух случаях вы обычно просто использовать конечный монадическое значение результата, а не с помощью функции «Run» ,

[0]: Где нормальное означает не использование магии компилятора, наиболее очевидной «ненормальной» монадой, конечно, является IO.

+0

Где происходит привязка '>> ='? – CMCDragonkai

+1

@CMCDragonkai: Используется для перевода блоков 'do'. Строка типа 'x <- foo' становится связующим' foo >> = \ x -> ', где тело лямбда (переведенная версия) остается в остальном блоке' do'. –