我发现似乎只有在 lisp 中才能定义这个:
(lambda (x)(+ x y 1))
在其他语言中,即使是函数式语言,也必须先定义变量,然后才能使用它
所以在lisp中,有“自由变量”,“绑定变量”的概念。其他语言有这个概念吗?因为所有变量都必须先定义,所以所有变量都是“绑定变量”?变量有一个初始值,在全局或其他范围?
我认为 lambda 概念很早,但是如果有任何变量或值,必须先定义然后使用,是不是更容易理解和使用?
谢谢!
我发现似乎只有在 lisp 中才能定义这个:
(lambda (x)(+ x y 1))
在其他语言中,即使是函数式语言,也必须先定义变量,然后才能使用它
所以在lisp中,有“自由变量”,“绑定变量”的概念。其他语言有这个概念吗?因为所有变量都必须先定义,所以所有变量都是“绑定变量”?变量有一个初始值,在全局或其他范围?
我认为 lambda 概念很早,但是如果有任何变量或值,必须先定义然后使用,是不是更容易理解和使用?
谢谢!
我认为这个问题有两个部分。Lisp(和其他语言)中有一个关于自由变量访问的一般问题,这是一个很大的主题,因为有很多很多 Lisp 甚至更多其他语言,包括对某些变量(CL)具有动态范围的语言和具有只有动态范围(直到最近,还有许多其他旧实现)。还有一个问题是什么时候需要在 Lisps(和其他语言)中解决变量引用,特别是需要前向引用以及何时以及如何解决它们。
在这个答案中,我只讨论第二部分,关于前向引用,我认为这是直接让你感到困惑的地方。我将使用 Racket 和r5rs
语言给出示例,因为这是您在评论中使用的。
首先,任何支持递归而不跳过极端循环(也就是使用 Y 组合器)的语言都需要支持对尚未绑定的名称的引用,这些名称称为前向引用。考虑一下:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(+ 1 (foo (cdr x))))))
好的,所以当函数被评估时,有一个foo
递归调用的引用。 But foo
is not yet bound at that point,因为foo
将被绑定到函数本身,这就是我们要定义的。事实上,如果你这样做:
(let ((r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x)))))))
r)
这是一个错误,因为r
确实尚未定义。相反,您需要使用letrec
which 会产生合适的魔法来确保一切正常。
好吧,您可能会争辩说这letrec
会变成这样:
(let ((r #f))
(set! r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x))))))
r)
也许确实如此。但是这个呢?
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(bar x))))
(define bar
(lambda (x)
(+ 1 (foo (cdr x)))))
并 for on 以获得更详细的程序。
所以这里有一个关于前向引用的普遍问题。以这样一种方式编写程序基本上是不可能的,即在程序的文本中没有对未知名称的引用。请注意,在上面的程序中foo
引用bar
和bar
引用,foo
因此没有定义的顺序foo
,并且bar
不涉及对尚未绑定在源中的名称的引用。
请注意,这个问题基本上会影响所有编程语言:这不是 Lisp 家族语言所独有的。
那么,这是如何解决的呢?嗯,答案是“这取决于你关心的特定语言是如何定义的”。我不完全确定 R5RS 规范对此有何评论,但我想我确信Racket 对此有何评论。
Racket 说的是,前向引用都需要在模块级别进行整理。所以如果我有这样的球拍源文件:
#lang r5rs
(define a
(lambda (x) (+ x y)))
(define y 1)
这很好,因为在模块的末尾都定义了a
和x
,所以定义a
也很好。foo
这与上面的定义没有什么不同bar
,请注意像这样的源文件:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(bar x))))
不合法:bar
不受约束:
$ racket ts.rkt
ts.rkt:7:9: bar: unbound identifier
[...]
因此,前向引用问题的答案有两个:
我认为这让你感到困惑。特别是仅包含以下内容的 Racket 模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
在球拍中是不合法的。但是这个模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
(define y 1)
是合法的,因为y
在前向引用得到解决时绑定。
Common Lisp 比这更复杂,原因有两个:
因此,例如,如果我们采用这样的方法(假设在顶层,之前没有其他用户定义):
(defun foo (x)
(+ x y))
theny
不能是对词法变量的引用,因为 的词法环境foo
是空的,因为没有顶级词法变量绑定。
所以y
必须是对动态变量的引用(CL 中的特殊变量)。但实际上,CL 通过说不允许这样的引用来解决动态变量的前向引用问题。所以上面的定义是不合法的CL。许多编译器会在编译时发出警告,但他们不必这样做。
然而,下面的变体很好(注意我使用了动态变量的 CL 约定)。
(defvar *y* 1)
(defun foo (x)
(+ x *y*))
这很好,因为在定义*y*
之前就知道了。foo
事实上我撒了谎:允许对动态变量的前向引用,但你必须告诉语言它们:
(defun foo (x)
(declare (special *y*))
(+ x *y*))
现在,如果有以后(defvar *y* ...)
(或同等)的电话foo
会很好。如果没有这样的全球特殊声明,*y*
那么我认为如果foo
被调用是未定义或错误会发生什么(我有点希望后者,但我不确定。
最后,即使没有对 的全局特别声明*y*
,这也很好:
(let ((*y* 3))
(declare (special *y*))
(foo 1))
这很好,因为*y*
在本地声明为特殊的(它有一个绑定声明)。
CL 中允许对函数的前向引用,并且关于何时需要解决它们有一些复杂的规则,我不确定我是否记得。
在 Common Lisp 中,使用未定义变量的确切效果是未定义的。
Common Lisp 编译器会发出警告:
* (lambda (x) (+ x y 1))
; in: LAMBDA (X)
; (+ X Y 1)
; --> +
; ==>
; (+ X Y)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::Y
;
; compilation unit finished
; Undefined variable:
; Y
; caught 1 WARNING condition
#<FUNCTION (LAMBDA (X)) {226A95BB}>
关于首先定义的规则有一些例外,对于所有这些例外,他们在提及某些标准值时定义它们。例如。JavaScript 使用undefined
,perl 使用undef
等。
绑定变量是当前函数范围的变量。自由变量来自嵌套函数范围或全局,但它们需要在运行时存在。
(define test
((lambda (a)
(lambda (b)
(list g a b)))
5))
(define g 0)
(test 10)
所以在b
作为绑定变量的内部函数中。g
并且a
是自由变量,因为它们不是来自该函数的参数。这就是 free 的意思,g
不需要在函数中提及之前定义,但需要在调用提及的代码之前存在。
在词法范围的语言中5
,将绑定到a
这样的结果,(0 5 10)
而在动态范围的语言中,例如。PicoLisp 的语法略有不同,答案将是(0 2 10)
因为创建中的绑定a
在其test
内部代码完成的第二秒超出范围。
几乎所有语言都是词法范围的。例如。所有 OP 语言标签(Ocaml、F#、Haskell、C# 和 Clojure)都是词法范围的。