在非常好的Write yourself a Scheme in 48 hours的chapter 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.如果我们希望经常使用insert
或insertWith
进行更改,那么使用Map String LispVal
是一个糟糕的选择吗?
1条答案
按热度按时间wwodge7n1#
Map String LispVal
显然比[(String, LispVal)]
好,使用列表的唯一原因是传统:Scheme解释器是出了名的简约主义,并且经常在Scheme中实现,Scheme的基本数据结构是一个对,从这个对可以很容易地建立关联列表,但是Map需要更多的工作。如果你可以很容易地在你的实现语言中访问一个真实的的Map,那么就使用它,除非你想假装现在还是20世纪70年代。1.对于您目前定义的LispVal类型,真实的上并不需要任何IORef。我认为作者使用IORef是为了处理lambda和闭包。闭包将为其环境捕获Env。如果按照您的想象实现,没有IORef,则类似于
必须返回1,而2是所需的结果。解释器需要一种方法,使
set!
表达式能够修改已经捕获的环境。我还没有仔细考虑过是否真的需要两层IORef,或者一层就足够了,不过本书的作者可能已经考虑过了。