Common Lisp:有什么方法可以避免 defvar 或 defparameter?

Common Lisp: Any way to avoid defvar or defparameter?

我正在使用 SBCL 2.0.1.debian 和 Paul Graham 的 ANSI Common Lisp 来学习 Lisp。

虽然在第 2 章,我意识到我不能像作者那样使用 setf!稍微谷歌一下,我了解到我必须使用 defvardefparameter 来 'introduce' 我的全局变量,然后才能使用 setq!

设置它们

有什么方法可以避免通过 defvardefparameter 从 SBCL 内部或通过开关从外部引入全局变量?其他 Lisp 也强制执行此操作吗?

我理解它们在大型代码库中的增值,但现在我只是通过编写小程序来学习,所以我发现它们很麻烦。我习惯在其他语言中使用全局变量,所以不必介意全局/局部变量错误。

如果您正在编写程序,在某些持久存在于文件的意义上,则使用 def* 形式。未定义变量的顶级 setf / setq 在 CL 中具有未定义的语义,更糟糕的是,在不同的实现中具有不同的语义。输入 defvardefparameterdefconstant 并不比输入 setfsetq 难多少,这意味着您的程序将定义语义,这通常被认为是一个很好的事物。所以不,对于程序来说,没有办法避免使用 def* 形式,或类似的形式。

如果你只是简单地在 REPL / listener 中输入东西来玩东西,那么我认为只在顶层使用 setf 就可以了(没有人使用在 REPL 中输入东西的环境我觉得真的很执着)。

你说你习惯于在其他语言中使用全局变量。根据其他语言的不同,这很可能意味着您 习惯了 CL 的语义,以使用 def* 形式定义的绑定,这不仅是全局的,而且 全局特殊,或全局动态。我不知道还有哪些其他语言甚至有 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

因此请考虑 'equivalent' 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 已被 [=23= 声明为全局特殊变量],这意味着调用 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* 形式定义的东西是 globally 特殊/动态的,这意味着 all 它们的绑定是动态的,这就是为什么你应该始终区分这些变量的名称,使用 * 约定:(defvar *my-var* ...)never (defvar my-var ...). (不过 (defconstant my-constant ...) 没问题。)