【发布时间】:2012-05-16 13:32:58
【问题描述】:
我一直在关注和扩展教程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 的想法是,我将能够通过评估器自动传递环境(维护变量绑定),并且它的使用位置很明显,因为将调用@ 987654325@。这似乎比将环境作为额外参数显式传递更可取。当我开始实现延续时,我会想用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类型,因为我在环境中使用IORefs作为值,所以我只能写一个带有签名Env -> IO Env的函数。
这是否可能,还是我需要使用StateT 而不是ReaderT?
【问题讨论】:
标签: haskell monad-transformers