2010-08-10 97 views
13

我是haskell的新手,我必须编写一个上下文感知的程序,所以我认为我可以使用Reader Monad来保持从文件中读取上下文,我知道如何读取文件在像[([Char],[Char])]之类的元组列表中的内容,但是我不知道如何实现Reader Monad,使得我的程序的所有组件无需使用强制风格就可以使用环境,特别是我不知道如何设置和使用环境,据我了解,我应该把它作为所有需要使用runReader函数env的环境的函数的参数,但是我很困惑,有人可以给我一些指示或者一个好东西教程?在此先感谢帮助阅读器monad

+0

你确定首先需要'Reader'吗? “让所有组件可用的环境”通常不是在Haskell中编写代码的最佳方式。你能更详细地描述你正在处理的任务吗? – 2010-08-10 19:50:28

+3

@Travis Brown:如果您拥有大量本质上静态的数据,那么在整个程序中的许多地方都需要它,这只有在运行时才可用,例如通过加载数据文件。例如,想象一个程序,其中所有文本都是在程序启动时从资源文件本地化和加载的。 – 2010-08-10 20:34:53

+0

事实上,如果有什么听起来可疑的话,那就是'[([Char],[Char])]''的类型。知道它是一个环境,它听起来像一个字符串字典,它应该至少成为一个'Data.Map.Map字符串字符串',而不是像一个可爱的[字符串字符串字符串](http: //hackage.haskell.org/package/bytestring-trie)。 – 2010-08-10 20:37:12

回答

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 

现在我将更改它以使用Reader monad来传递环境。由于代码已经在IO中,因此有必要使用monad变换器版本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 

所以这就是你如何使用阅读器。每个使用环境参数的功能都不再需要。不采取环境的功能可以直接使用,或者如果它们是单一的,可以使用。

8

使用任何“正常”monad的基本方案[0]几乎是相同的全面。本质:

  • 写函数返回一元类型的值,使用do符号,如果你喜欢,就像你写的IO功能像main
  • 使用您正在使用的monad的任何特定功能。
  • 呼叫相互这些功能,使用标准的规则:“内部”,引起了其他值
    • 绑定使用<-相同单子的值来获得的价值是“跑” 。
    • 使用let绑定任何其他值,使其独立于monadic结构。
  • 使用特定monad的专门“运行”函数来评估单子计算并获得最终结果。

这样做,并且monad描述的额外功能(在这种情况下,传递一个额外的环境参数)附加功能的所有细节都会自动处理。

现在,一般的阅读器操作asklocal

  • ask是一个单子值保持环境;在do块中,您可以像使用getLine那样在IO monad中使用相同的方法来使用它。
  • local提供了一个函数,它在Reader monad中提供一个新的环境和一个计算,在前者修改的环境中运行后者,然后将结果放入当前函数中。换句话说,它使用本地修改的环境运行子计算。

“运行”功能是创造性地命名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是初始环境。

几乎完全一样的结构可以与其他单子一起使用,如StateMaybe[],虽然在后两种情况下,你通常只需要使用一元的最终结果值,而不是使用“运行”功能。

[0]:凡正常意味着不涉及编译器魔术,最明显的“异常”monad当然是IO

+0

“'=”绑定在哪里起作用? – CMCDragonkai 2014-09-04 07:45:05

+1

@CMCDragonkai:它用于翻译'do'块。像'x < - foo'这样的行变成了一个绑定'foo >> = \ x - >',其中lambda的主体是'do'块剩余部分的(翻译后的版本)。 – 2014-09-04 14:39:23