问题描述
我正在使用 SBCL 2.0.1.debian 和 Paul Graham 的 ANSI Common Lisp 来学习 Lisp。
尽管在第 2 章中,我意识到我不能像作者那样使用 setf
!稍微谷歌搜索,我了解到我必须使用 defvar
或 defparameter
来“引入”我的全局变量,然后才能使用 setq
设置它们!
有什么办法可以避免通过 defvar
或 defparameter
引入全局变量,无论是从 SBCL 内部还是从外部通过开关?其他 Lisp 是否也要求这样做?
我了解它们在大型代码库中的价值,但现在我只是通过编写小型程序来学习,因此我发现它们很麻烦。我习惯于在其他语言中使用全局变量,所以不必介意全局/局部变量错误。
解决方法
如果您正在编写程序,从某种意义上说,文件中存在一些持久存在的东西,那么请使用 def*
形式。未定义变量的顶级 setf
/ setq
在 CL 中具有未定义的语义,更糟糕的是,在不同的实现中具有不同的语义。键入 defvar
、defparameter
或 defconstant
并不比键入 setf
或 setq
难多少,这意味着您的程序将具有定义的语义,这通常被认为是一种好的事物。所以不,对于程序,没有办法避免使用 def*
形式,或某种等效形式。
如果你只是在 REPL/监听器上输入东西来玩东西,那么我认为只在顶级使用 setf
就可以了(没有人使用在 REPL 输入的东西真的持久的环境我想多了)。
您说您习惯于在其他语言中使用全局变量。取决于那些其他语言是什么,这很可能意味着您不习惯于使用 def*
形式定义的绑定的 CL 语义,这不仅是全局的,而且全局特殊,或全局动态。我不知道哪些其他语言甚至具有 CL 的特殊/词汇区别,但我怀疑没有那么多。例如考虑这个 Python (3) 程序:
x = 1
def foo():
x = 0
print(x)
def bar():
nonlocal x
x += 1
return x
return bar
y = foo()
print(y())
print(y())
print(x)
如果你运行它会打印
0
1
2
1
所以考虑“等效”的 CL 程序(注意:永远不要用这样命名的变量编写程序):
(defvar x 1)
(defun foo ()
(let ((x 0))
(print x)
(lambda ()
(incf x))))
(let ((y (foo)))
(print (funcall y))
(print (funcall y))
(print x)
(values))
如果你运行它会打印
0
2
3
3
我认为您可以同意这与 Python 程序打印的内容大不相同。
那是因为 CL 中的顶级变量是特殊的,或动态变量:在上面的 CL 程序中,x
是动态范围的,因为 x
已被 defvar
声明为全局特殊的,这意味着对 foo
返回的函数的调用正在改变 x
的全局值。
Python 根本没有动态绑定(但您可以编写 Python 模块,将它们模拟为访问显式绑定堆栈的函数,但有点狡猾)。类似的事情也是其他语言公开动态绑定的方式。例如,Racket 就是这样做的:
(define d (make-parameter 1))
(define (foo)
(parameterize ([d 0])
(display (d))
(λ ()
(d (+ (d) 1))
(d))))
(let ([y (foo)])
(display (y))
(display (y))
(display (d)))
将打印
0
2
3
3
虽然这个程序
(define x 1)
(define (foo)
(let ([x 0])
(displayln x)
(λ ()
(set! x (+ x 1))
x)))
(let ([y (foo)])
(displayln (y))
(displayln (y))
(displayln x))
将打印
0
1
2
1
就像 Python 那样。
如果不是 CL 的兼容性要求,这也许也是 CL 应该这样做的方式(这显然是个人意见,我对 CL 做的方式感到满意)。
CL 根本没有顶级(全局)词法 绑定(但您可以编写一个 CL 程序,该程序将使用符号宏以非常令人信服的方式模拟它们)。如果你想要这样的东西,我怀疑互联网上已经有一些东西,或者我可以继续整理我的实现。作为一个使用这种东西的程序的例子:
(defglex x 1)
(defun foo ()
(let ((x 0))
(print x)
(lambda ()
(incf x))))
(let ((y (foo)))
(print (funcall y))
(print (funcall y))
(print x))
将打印
0
1
2
1
CL 具有词法和动态(特殊)非全局绑定。
注意:使用 def*
形式定义的事物是全局特殊/动态的,这意味着它们的所有绑定都是动态的,这就是为什么您应该始终使用 *
约定来区分此类变量的名称:(defvar *my-var* ...)
和 never (defvar my-var ...)
。 (不过,(defconstant my-constant ...)
没问题。)