The*_*one 1 scheme closures racket
我\xe2\x80\x99最近冒险进入了编写Scheme解释器的伟大领域,并且我\xe2\x80\x99遇到了一个障碍:闭包。据我了解,它们封装了一个本地环境,其中包含一个每次调用闭包时都会恢复的过程(这可能不完全正确)。我在网上任何地方都能找到的问题是如何正式定义闭包,即在 EBNF 语法中。我见过的大多数例子都说闭包是一个零参数的过程,它有一个嵌套在 let 表达式中的 lambda 表达式。这是定义Scheme闭包的唯一方法吗?更重要的是,如果\xe2\x80\x99s没有正式的方法来正式定义闭包,那么你实际上如何解释它?如果将所有 let 表达式转换为 lambda 会发生什么?例如,如果我这样声明一个闭包
\n(define (foo) (let ((y 0)) (\xce\xbb (x) (\xe2\x80\xa6))))\nRun Code Online (Sandbox Code Playgroud)\n然后将其赋值给一个变量
\n(define bar (foo))\nRun Code Online (Sandbox Code Playgroud)\n按什么顺序评估?从我\xe2\x80\x99所看到的,当foo声明时,它存储一个指向父环境的指针,并声明它自己的环境。如果我调用(bar),我应该立即替换到保存的本地环境中吗?
今天,我认为将闭包视为某种特殊的神奇事物没有什么帮助:很久以前,在Scheme史前的语言中它们就是这样,但在现代语言中它们根本不是一个特殊的东西:它们只需以明显的方式遵循语言的语义即可。
\n两件重要的事情(这些都是R7RS的引用,都来自第 1.1 节):
\n\n\nScheme 是一种静态范围的编程语言。变量的每次使用都与该变量的词汇表观绑定相关联。
\n
和
\n\n\n在Scheme计算过程中创建的所有对象(包括过程和延续)都具有无限的范围。
\n
这意味着Scheme是一种具有词法范围和无限范围的语言:只要存在引用的可能性,任何变量绑定都存在。而且,方便的是,您始终可以静态地(即通过阅读代码)了解一段代码可能引用的绑定。
\n这里重要的是,这些规则非常简单:没有奇怪的特殊情况。如果对变量绑定的引用在一段代码中可见(您可以通过查看代码来判断),那么它就是可见的。它仅有时不可见,或仅在某个时间间隔内,或仅当月亮是凸月时:它是可见的。
\n但这些规则的含义是,过程在某种程度上需要记住它们引用或可能引用的所有绑定,以及它们创建时在范围内的绑定。因为作用域是静态的,所以总是可以确定哪些绑定在作用域内(免责声明:我不确定这对于全局绑定来说是如何正式工作的)。
\n因此,闭包的非常老式的定义将是在其引用的绑定存在的范围内定义的过程。这将是一个闭包:
\n(define x\n (let ((y 1))\n (\xce\xbb (z)\n (set! y (+ y z))\n y)))\nRun Code Online (Sandbox Code Playgroud)\n这个过程将返回一个闭包:
\n(define make-incrementor\n (\xce\xbb (val)\n (\xce\xbb ()\n (let ((v val))\n (set! val (+ val 1))\n v))))\nRun Code Online (Sandbox Code Playgroud)\n但是您可以看到,在这两种情况下,这些事物的行为都直接遵循语言的范围和范围规则:没有特殊的“这是一个闭包”规则。
\n在第一种情况下,最终作为两者值的函数x引用并改变了绑定y以及引用了调用z时建立的绑定。
在第二种情况下,调用make-incrementor为 建立一个绑定val,然后该绑定被它返回的函数引用并改变。
我不确定将所有lets 变成\xce\xbbs 是否有助于理解事物,但第二件事变成
(define make-incrementor\n (\xce\xbb (val)\n (\xce\xbb ()\n ((\xce\xbb (v)\n (set! val (+ val 1))\n v)\n val))))\nRun Code Online (Sandbox Code Playgroud)\n现在您可以看到,调用返回的函数make-incrementor时,现在立即调用另一个v仅绑定到其参数的函数,该参数本身就是由 建立的绑定的值make-incrementor:它这样做只是为了保留 pre - 当然,该绑定的增量值。
同样,规则很简单:您只需查看代码即可了解它的作用。没有特殊的“关闭”情况。
\n如果您确实想要产生这种情况的形式语义,那么 R7RS 的 7.2 具有该语言的形式语义。
\n