2

我一直在关注并扩展教程Write Yourself A Scheme。我有一种LispVal包裹在几层单子变压器中的类型:

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

using 的想法ReaderT是,我将能够通过评估器自动传递环境(它维护变量绑定),并且它的使用位置很明显,因为将调用ask. 这似乎比将环境作为额外参数显式传递更可取。当我开始实现延续时,我会想用ContTmonad 转换器做一个类似的技巧,并避免为延续传递额外的参数。

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

举一个具体的例子,假设每当我评估一个 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是一个将 a 分配Bool给 any的函数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需要有 type Env -> Env,而且由于我IORef在环境中使用 s 作为值,所以我只能写一个带有签名的函数Env -> IO Env

这甚至可能吗,还是我需要使用StateT而不是ReaderT

4

2 回答 2

7

ReaderT当您有范围内的只读环境时使用。例如在口译员中。环境ReaderT嵌套在词法上,因此您可以在每次遇到绑定时扩充环境。像这样:

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

这是我的一篇旧帖子,其中包含一些示例。

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

于 2012-05-16T13:38:53.297 回答
3

If you want a modifiable context, StateT is what you need rather than ReaderT. Reader will only allow you to ask, not tell (as in WriterT). State is a composition of Reader and Writer, allowing you to get, put, and modify at will.

于 2012-05-16T13:39:12.700 回答