使用ReaderT创建可修改的环境

时间:2012-05-16 13:32:58

标签: haskell monad-transformers

我一直关注并扩展教程Write Yourself A Scheme。我有一个类型LispVal包含在几层monad变换器中:

import qualified Data.Map as M

data LispVal   = ...
data LispError = ...

type Bindings = M.Map String (IORef LispVal)
data Env = Environment { parent :: Env, bindings :: IORef Bindings }

type IOThrowsError = ErrorT LispError IO
type EvalM = ReaderT Env IOThrowsError

使用ReaderT的想法是,我将能够通过评估器自动传递环境(维护变量绑定),并且在使用它的地方很明显,因为会有一个调用到ask。这似乎比将环境明确地作为额外参数传递更好。当我来实现continuation时,我想用ContT monad变换器做一个类似的技巧,并避免为继续传递额外的参数。

但是,我还没有弄清楚如何通过这样做来修改环境。例如,定义新变量或设置旧变量的值。

对于一个具体的例子,让我们说每当我评估一个if语句时,我想将变量it绑定到test子句的结果。我的第一个想法是直接修改环境:

evalIf :: [LispVal] -> EvalM LispVal
evalIf [test, consequent, alternate] = do
  result <- eval test
  bind "it" result
  case (truthVal result) of
    True  -> eval consequent
    False -> eval alternate

此处truthVal是一个将Bool分配给任何LispVal的函数。但我无法弄清楚如何编写函数bind以便它修改环境。

我的第二个想法是使用local

evalIf :: [LispVal] -> EvalM LispVal
evalIf [test, consequent, alternate] = do
  result <- eval test
  local (bind "it" result) $ case (truthVal result) of
    True  -> eval consequent
    False -> eval alternate

但是这里bind需要有Env -> Env类型,因为我在环境中使用IORef s作为值,所以我只能编写一个带有签名{{1}的函数}}

这是否可行,或者我是否需要使用Env -> IO Env代替StateT

2 个答案:

答案 0 :(得分:7)

当您具有作用域的只读环境时,将使用

ReaderT。例如。在口译员。 ReaderT中的环境在词汇上嵌套,因此您可以在每次遇到绑定时扩充环境。像这样:

eval (LetE x e1 e2) = do
    env <- ask
    v   <- eval e1
    local (M.insert x v) (eval e2)

我的an old post有一些例子。

如果你有这样的环境,除非你玩动态范围的有趣游戏,否则没有必要也使用IORefs? IORefs的原因是什么?

答案 1 :(得分:3)

如果您想要一个可修改的上下文,StateT就是您所需要的而不是ReaderT。读者只允许您ask,而不是tell(如WriterT中所述)。 State是Reader and Writer的组合,允许您随意getputmodify