wan*_*kai 1 lisp undefined-variable
我发现似乎只有在 lisp 中才能定义这个:
(lambda (x)(+ x y 1))
Run Code Online (Sandbox Code Playgroud)
在其他语言中,即使是函数式语言,也必须先定义一个变量,然后才能使用它
所以在lisp中,有“自由变量”,“绑定变量”的概念。其他语言有这个概念吗?因为所有的变量都必须先定义,所以所有的变量都是“绑定变量”?变量有一个初始值,在全局或其他范围?
我认为 lambda 的概念很早,但是如果任何变量或值,必须先定义然后使用,是不是更容易理解和使用?
谢谢!
小智 5
我认为这个问题有两个部分。Lisp(和其他语言)中有一个关于自由变量访问的一般问题,这是一个很大的主题,因为有很多很多 Lisp 甚至更多其他语言,包括具有某些变量的动态范围 (CL) 的和具有只有动态范围(elisp 直到最近,还有许多其他旧实现)。还有一个问题是关于何时需要在 Lisps(和其他语言)中解析变量引用,尤其是需要向前引用以及何时以及如何解析它们。
在这个答案中,我只讨论第二部分,关于前向引用,我认为这直接让你感到困惑。我将给出使用 Racket 和r5rs语言的示例,因为这就是您在评论中使用的内容。
首先,任何支持递归而不跳过极端循环的语言(也就是使用 Y 组合器)需要支持对尚未绑定的名称的引用,这称为前向引用。考虑一下:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(+ 1 (foo (cdr x))))))
Run Code Online (Sandbox Code Playgroud)
好的,所以当函数被评估时foo,递归调用有一个引用。 但此时foo尚未绑定,因为foo将绑定到函数本身,这就是我们正在定义的。事实上,如果你做这样的事情:
(let ((r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x)))))))
r)
Run Code Online (Sandbox Code Playgroud)
这是一个错误,因为r确实还没有定义。相反,您需要使用letrecwhich 执行合适的魔法来确保一切正常。
好吧,你可能会争辩letrec说它变成了这样:
(let ((r #f))
(set! r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x))))))
r)
Run Code Online (Sandbox Code Playgroud)
也许确实如此。但这又如何呢?
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(bar x))))
(define bar
(lambda (x)
(+ 1 (foo (cdr x)))))
Run Code Online (Sandbox Code Playgroud)
而对于更精细的程序。
所以这里有一个关于前向引用的普遍问题。以这样一种方式编写程序基本上是不可能的,即在程序的文本中不存在对未知名称的引用。请注意,在上面的程序中foo引用bar 和bar引用foo因此没有定义foo和的顺序,bar其中不涉及对尚未绑定到源代码中的名称的引用。
请注意,这个问题基本上会影响所有编程语言:这并不是 Lisp 家族语言所独有的。
那么,这是如何解决的呢?嗯,答案是“这取决于您关心的特定语言是如何定义的”。我不完全确定 R5RS 规范对此有何评论,但我想我确定 Racket对此有何评论。
Racket 所说的是,前向引用都需要在模块级别进行整理。因此,如果我有这样的 Racket 源文件:
#lang r5rs
(define a
(lambda (x) (+ x y)))
(define y 1)
Run Code Online (Sandbox Code Playgroud)
这是好的,因为在模块的端部都a与x被定义,所以定义a为细。这foo和bar上面的定义没有什么不同,请注意像这样的源文件:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(bar x))))
Run Code Online (Sandbox Code Playgroud)
不合法:bar不受约束:
$ racket ts.rkt
ts.rkt:7:9: bar: unbound identifier
[...]
Run Code Online (Sandbox Code Playgroud)
因此,前向引用问题的答案有两个方面:
我认为这就是让您感到困惑的原因。特别是仅包含以下内容的 Racket 模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
Run Code Online (Sandbox Code Playgroud)
在 Racket 中是不合法的。但是这个模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
(define y 1)
Run Code Online (Sandbox Code Playgroud)
是合法的,因为y在解析前向引用时绑定。
由于两个原因,Common Lisp 比这更复杂:
因此,例如,如果我们采取这样的事情(假设在顶层,在它之前没有其他用户定义):
(defun foo (x)
(+ x y))
Run Code Online (Sandbox Code Playgroud)
theny 不能是对词法变量的引用,因为 的词法环境foo是空的,因为没有顶级词法变量绑定。
所以y必须是对动态变量的引用(CL 中的特殊变量)。但实际上,CL 通过说不允许这样的引用来解决动态变量的前向引用问题。所以上面的定义不是合法的CL。许多编译器会在编译时发出警告,但他们不必这样做。
但是,以下变体很好(请注意,我对动态变量使用了 CL 约定)。
(defvar *y* 1)
(defun foo (x)
(+ x *y*))
Run Code Online (Sandbox Code Playgroud)
这很好,因为*y*在foo定义之前就知道了。
事实上,我撒了谎:允许对动态变量进行前向引用,但你必须告诉他们的语言:
(defun foo (x)
(declare (special *y*))
(+ x *y*))
Run Code Online (Sandbox Code Playgroud)
现在,如果稍后(defvar *y* ...)(或等效)调用foo将没问题。如果没有这样的全球特别声明,*y*那么我认为如果foo被调用会发生什么是未定义或错误(我有点希望是后者,但我不确定。
最后,即使没有对 的全球特别声明*y*,这也很好:
(let ((*y* 3))
(declare (special *y*))
(foo 1))
Run Code Online (Sandbox Code Playgroud)
这很好,因为*y*在本地声明为特殊的(有一个绑定声明)。
CL 中允许对函数的前向引用,并且有关于何时需要解析它们的复杂规则,我不确定我是否记得。
| 归档时间: |
|
| 查看次数: |
158 次 |
| 最近记录: |