haskell 将IORef用于解释器环境[在48小时内为自己编写一个方案]

wkyowqbh  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(149)

在非常好的Write yourself a Scheme in 48 hourschapter 7中,引入了一个在解释语言(Scheme,其中变量是可变的)中处理变量的环境,如下所示

type Env = IORef [(String, IORef LispVal)]

-- where LispVal is a simple type representing a lisp value:
data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | String String
             | Bool Bool

非TL;dr(TL;下面的dr)

State单子不是一个好的选择是有原因的(环境将被传递,最终需要存在于runState的“外部”),因此选择IORef。然而,我想知道为什么不直接用Map String LispVal表示为一个环境呢?如果我们后来用Map.insert改变了一个变量,而这个变量根本不应该复制整个Map,那么这里真的需要IORef的可变性吗?换句话说,很明显IORef是这里要走的路吗?此外,我的印象是,如果可能的话,最好避免使用IORef,一个典型的用例是从外部框架中的回调之类的东西中“泄漏”值。
不过,我很难想出一个比较这两种选择性能的好实验,似乎GHC对我的确定性重复调用优化得太好了。

tl;dr

1.在上面的示例中使用IORef是否很明显,无论是在性能方面还是在其他注意事项方面(可变性、代码中的IO,否则这些代码将是纯的)?
1.如果我们希望经常使用insertinsertWith进行更改,那么使用Map String LispVal是一个糟糕的选择吗?

wwodge7n

wwodge7n1#

  1. Map String LispVal显然比[(String, LispVal)]好,使用列表的唯一原因是传统:Scheme解释器是出了名的简约主义,并且经常在Scheme中实现,Scheme的基本数据结构是一个对,从这个对可以很容易地建立关联列表,但是Map需要更多的工作。如果你可以很容易地在你的实现语言中访问一个真实的的Map,那么就使用它,除非你想假装现在还是20世纪70年代。
    1.对于您目前定义的LispVal类型,真实的上并不需要任何IORef。我认为作者使用IORef是为了处理lambda和闭包。闭包将为其环境捕获Env。如果按照您的想象实现,没有IORef,则类似于
(let ((x 1))
  (let ((f (lambda () x)))
    (set! x 2)
    (f)))

必须返回1,而2是所需的结果。解释器需要一种方法,使set!表达式能够修改已经捕获的环境。
我还没有仔细考虑过是否真的需要两层IORef,或者一层就足够了,不过本书的作者可能已经考虑过了。

相关问题