似乎只有 lisp 允许使用变量而不先定义它?
It seems only lisp allow use a variable without define it first?
我发现好像只有lisp可以这样定义:
(lambda (x)(+ x y 1))
在其他语言中,即使是函数式语言,变量也必须先定义,然后才能使用
所以在lisp中有"free variable","bound variable"的概念。其他语言有这个概念吗?因为所有的变量都要先定义,所以所有的变量都是"bound variable"?变量有一个初始值,在全局或其他范围内?
我觉得lambda的概念很早,但是如果任何变量,或者值,必须先定义再使用,不是更容易理解和使用吗?
谢谢!
在 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}>
我认为这个问题有两个部分。在 Lisp(和其他语言)中有一个关于自由变量访问的普遍问题,这是一个很大的主题,因为有很多很多 Lisp 甚至更多其他语言,包括对某些变量(CL)具有动态范围的语言和具有仅 动态范围(elisp 直到最近,许多其他旧实现)。还有一个问题是关于何时需要在 Lisps(和其他语言)中解析变量引用,特别是需要前向引用以及何时以及如何解析它们..
在这个回答中,我只讨论第二部分,关于前向引用,我认为这是直接让您感到困惑的地方。我将使用 Racket 和 r5rs
语言给出示例,因为这就是您在评论中使用的语言。
首先,任何支持递归而不跳过极端循环的语言(aka 使用 Y 组合器)需要支持对尚未绑定的名称的引用,这些名称是称为转发引用。考虑一下:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(+ 1 (foo (cdr x))))))
OK,所以当函数被评估时,有一个对 foo
的引用用于递归调用。 但是 foo
在那一点上还没有绑定 ,因为 foo
将绑定到函数本身,这就是我们定义的。事实上,如果你这样做:
(let ((r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x)))))))
r)
这是一个错误,因为r
确实还没有定义。相反,您需要使用 letrec
来执行适当的魔法以确保一切正常。
嗯,你可能会争辩说 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)))))
还有更详细的程序。
所以这里有一个关于前向引用的普遍问题。基本上不可能以这样一种方式编写程序,即在程序的文本中,没有对未知名称的引用。注意上面程序中 foo
指的是 bar
而 bar
指的是 foo
所以有 no foo
和 bar
定义的 order 不涉及对尚未在源中绑定的名称的引用。
请注意,此问题基本上影响所有编程语言:这不是 Lisp-family 语言独有的。
那么,这个问题是如何解决的呢?嗯,答案是'it depends on how the specific language you care about is defined'。我不完全确定 R5RS 规范对此有何评论,但我想我 确定 Racket 对此有何评论。
Racket说的是前向引用都需要在模块级别进行整理。所以如果我有这样的球拍源文件:
#lang r5rs
(define a
(lambda (x) (+ x y)))
(define y 1)
这很好,因为在模块的末尾同时定义了a
和x
,所以[=36=的定义】 还好。这与上面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
[...]
所以forward-reference问题的答案是two-fold:
- 用几乎所有实用语言编写程序,尤其是在 Lisp 中,需要对尚未在引用点绑定的名称进行前向引用;
- 语言规范需要定义何时以及如何解析此类前向引用。
我认为这就是让您感到困惑的地方。特别是一个 Racket 模块,它只包含这个:
#lang r5rs
(define a (lambda (x) (+ x y)))
在 Racket 中是不合法的。但是这个模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
(define y 1)
是合法的,因为 y
是在解析前向引用时绑定的。
Common Lisp 比这更复杂,原因有二:
- 它是 Lisp-2,所以函数和变量绑定存在于不同的命名空间中;
- 没有标准的顶层词法变量绑定。
例如,如果我们采取这样的事情(假设在顶层,前面没有其他用户定义):
(defun foo (x)
(+ x y))
那么y
不能是词法变量的引用,因为foo
的词法环境是空的,因为没有顶层词汇变量绑定。
所以y
必须是对动态变量(CL 语言中的特殊变量)的引用。但实际上,CL通过声明不允许这样的引用来解决动态变量的forward-reference问题。所以上面的定义是不合法的CL。许多编译器会在编译时发出警告,但他们不必这样做。
但是,以下变体很好(请注意,我对动态变量使用了 CL 约定)。
(defvar *y* 1)
(defun foo (x)
(+ x *y*))
这很好,因为 *y*
在 foo
定义之前就已经知道了。
事实上我撒了谎:转发对动态变量的引用是允许的,但你必须告诉语言米:
(defun foo (x)
(declare (special *y*))
(+ x *y*))
而现在,如果稍后有 (defvar *y* ...)
(或等效)调用 foo
就可以了。如果 *y*
没有这样的 globally-special 声明,那么我认为如果调用 foo
会发生什么未定义或错误(我有点希望是后者,但我不确定.
最后,即使没有*y*
的全球特别声明,这也很好:
(let ((*y* 3))
(declare (special *y*))
(foo 1))
这很好,因为 *y*
在本地声明为特殊的(有一个绑定声明)。
在 CL 中允许对 函数 的转发引用,关于何时需要解析它们有复杂的规则,我不确定我是否记得。
关于首先定义的规则有一些例外,对于所有这些规则,他们在提及某个标准值时定义它们。例如。 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
不需要在函数中的 mention 之前定义,但需要在调用 mentions.
的代码之前存在。
在词法范围的语言中,5
将绑定到 a
,这样在动态范围的语言中,结果将是 (0 5 10)
,例如。 PicoLisp 的语法略有不同,答案是 (0 2 10)
,因为创建 test
时的绑定 a
在其内部代码完成后的第二个范围内就超出了范围。
几乎所有的语言都是词法范围的。例如。所有 OP 语言标签(Ocaml、F#、Haskell、C# 和 Clojure)都是词法范围的。
我发现好像只有lisp可以这样定义:
(lambda (x)(+ x y 1))
在其他语言中,即使是函数式语言,变量也必须先定义,然后才能使用
所以在lisp中有"free variable","bound variable"的概念。其他语言有这个概念吗?因为所有的变量都要先定义,所以所有的变量都是"bound variable"?变量有一个初始值,在全局或其他范围内?
我觉得lambda的概念很早,但是如果任何变量,或者值,必须先定义再使用,不是更容易理解和使用吗?
谢谢!
在 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}>
我认为这个问题有两个部分。在 Lisp(和其他语言)中有一个关于自由变量访问的普遍问题,这是一个很大的主题,因为有很多很多 Lisp 甚至更多其他语言,包括对某些变量(CL)具有动态范围的语言和具有仅 动态范围(elisp 直到最近,许多其他旧实现)。还有一个问题是关于何时需要在 Lisps(和其他语言)中解析变量引用,特别是需要前向引用以及何时以及如何解析它们..
在这个回答中,我只讨论第二部分,关于前向引用,我认为这是直接让您感到困惑的地方。我将使用 Racket 和 r5rs
语言给出示例,因为这就是您在评论中使用的语言。
首先,任何支持递归而不跳过极端循环的语言(aka 使用 Y 组合器)需要支持对尚未绑定的名称的引用,这些名称是称为转发引用。考虑一下:
#lang r5rs
(define foo
(lambda (x)
(if (null? x)
0
(+ 1 (foo (cdr x))))))
OK,所以当函数被评估时,有一个对 foo
的引用用于递归调用。 但是 foo
在那一点上还没有绑定 ,因为 foo
将绑定到函数本身,这就是我们定义的。事实上,如果你这样做:
(let ((r (lambda (x)
(if (null? x)
0
(+ 1 (r (cdr x)))))))
r)
这是一个错误,因为r
确实还没有定义。相反,您需要使用 letrec
来执行适当的魔法以确保一切正常。
嗯,你可能会争辩说 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)))))
还有更详细的程序。
所以这里有一个关于前向引用的普遍问题。基本上不可能以这样一种方式编写程序,即在程序的文本中,没有对未知名称的引用。注意上面程序中 foo
指的是 bar
而 bar
指的是 foo
所以有 no foo
和 bar
定义的 order 不涉及对尚未在源中绑定的名称的引用。
请注意,此问题基本上影响所有编程语言:这不是 Lisp-family 语言独有的。
那么,这个问题是如何解决的呢?嗯,答案是'it depends on how the specific language you care about is defined'。我不完全确定 R5RS 规范对此有何评论,但我想我 确定 Racket 对此有何评论。
Racket说的是前向引用都需要在模块级别进行整理。所以如果我有这样的球拍源文件:
#lang r5rs
(define a
(lambda (x) (+ x y)))
(define y 1)
这很好,因为在模块的末尾同时定义了a
和x
,所以[=36=的定义】 还好。这与上面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
[...]
所以forward-reference问题的答案是two-fold:
- 用几乎所有实用语言编写程序,尤其是在 Lisp 中,需要对尚未在引用点绑定的名称进行前向引用;
- 语言规范需要定义何时以及如何解析此类前向引用。
我认为这就是让您感到困惑的地方。特别是一个 Racket 模块,它只包含这个:
#lang r5rs
(define a (lambda (x) (+ x y)))
在 Racket 中是不合法的。但是这个模块:
#lang r5rs
(define a (lambda (x) (+ x y)))
(define y 1)
是合法的,因为 y
是在解析前向引用时绑定的。
Common Lisp 比这更复杂,原因有二:
- 它是 Lisp-2,所以函数和变量绑定存在于不同的命名空间中;
- 没有标准的顶层词法变量绑定。
例如,如果我们采取这样的事情(假设在顶层,前面没有其他用户定义):
(defun foo (x)
(+ x y))
那么y
不能是词法变量的引用,因为foo
的词法环境是空的,因为没有顶层词汇变量绑定。
所以y
必须是对动态变量(CL 语言中的特殊变量)的引用。但实际上,CL通过声明不允许这样的引用来解决动态变量的forward-reference问题。所以上面的定义是不合法的CL。许多编译器会在编译时发出警告,但他们不必这样做。
但是,以下变体很好(请注意,我对动态变量使用了 CL 约定)。
(defvar *y* 1)
(defun foo (x)
(+ x *y*))
这很好,因为 *y*
在 foo
定义之前就已经知道了。
事实上我撒了谎:转发对动态变量的引用是允许的,但你必须告诉语言米:
(defun foo (x)
(declare (special *y*))
(+ x *y*))
而现在,如果稍后有 (defvar *y* ...)
(或等效)调用 foo
就可以了。如果 *y*
没有这样的 globally-special 声明,那么我认为如果调用 foo
会发生什么未定义或错误(我有点希望是后者,但我不确定.
最后,即使没有*y*
的全球特别声明,这也很好:
(let ((*y* 3))
(declare (special *y*))
(foo 1))
这很好,因为 *y*
在本地声明为特殊的(有一个绑定声明)。
在 CL 中允许对 函数 的转发引用,关于何时需要解析它们有复杂的规则,我不确定我是否记得。
关于首先定义的规则有一些例外,对于所有这些规则,他们在提及某个标准值时定义它们。例如。 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
不需要在函数中的 mention 之前定义,但需要在调用 mentions.
在词法范围的语言中,5
将绑定到 a
,这样在动态范围的语言中,结果将是 (0 5 10)
,例如。 PicoLisp 的语法略有不同,答案是 (0 2 10)
,因为创建 test
时的绑定 a
在其内部代码完成后的第二个范围内就超出了范围。
几乎所有的语言都是词法范围的。例如。所有 OP 语言标签(Ocaml、F#、Haskell、C# 和 Clojure)都是词法范围的。