我一直关注并扩展教程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
?
答案 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的组合,允许您随意get
,put
和modify
。