Lisp 何时使用defparameter而不是setf?

Lisp 何时使用defparameter而不是setf?,lisp,common-lisp,variable-assignment,Lisp,Common Lisp,Variable Assignment,我目前正在阅读《LISP之地》(Land of LISP)一书,我刚刚读完第一章。在那里,有一个小程序编写的计算机猜测数字在1和100之间。其代码如下: (defparameter *small* 1) (defparameter *big* 100) (defun guess-my-number () (ash (+ *small* *big*) -1)) (defun smaller () (setf *big* (1- (guess-my-number))) (

我目前正在阅读《LISP之地》(Land of LISP)一书,我刚刚读完第一章。在那里,有一个小程序编写的计算机猜测数字在1和100之间。其代码如下:

(defparameter *small* 1)
(defparameter *big* 100)

(defun guess-my-number ()
    (ash (+ *small* *big*) -1))

(defun smaller ()
    (setf *big* (1- (guess-my-number)))
    (guess-my-number))

(defun bigger ()
    (setf *small* (1+ (guess-my-number)))
    (guess-my-number))

(defun start-over ()
    (defparameter *small* 1)
    (defparameter *big* 100)
    (guess-my-number))
到目前为止,我知道发生了什么,并在这方面帮了我很多。然而还有一件事让我困惑:据我所知,您使用
setf
为变量赋值,并使用
defparameter
最初定义变量。我还了解了
defparameter
defvar
之间的区别(至少我相信我了解;-)


所以现在我的问题是:如果我应该在变量初始化后使用
setf
为变量赋值,为什么
函数使用
defparameter
而不是
setf
?这有什么特别的原因吗,或者这只是草率的吗?

通常,人们会使用
defvar
来初始定义全局变量。
defvar
defparameter
之间的区别很细微,参见。这在这里起到了作用:
defparameter
(与
defvar
)重新赋值,而
defvar
将保留旧绑定。
说明使用内容:通常,
defvar
和friends用作顶级表单,而不是在某些函数中(闭包是
defun
上下文中最显著的例外)。我将使用
setf
,而不是
defparameter

,这样就有了全局变量。这些可以由
defcontent
(对于真正的非链接内容)、
defcameter
(一个可以更改的常量)和
defvar
(一个在
加载时不会覆盖的变量)来定义

您可以使用
setf
来更改词法变量和全局变量的状态。
start over
可以使用
setf
,因为代码并没有真正定义它,而是更改它。如果您将
defparameter
替换为
defvar
start over
将停止工作

(defparameter*par*5)
(setf*第6部分)
(defparameter*par*5)
*标准杆*;=>5杆
(defvar*var*5)
(setf*var*6)
(defvar*var*5)
*变量*;==>6
defconstant
defparameter
类似,只是一旦定义,CL实现就可以自由地将其内联到代码中。因此,如果重新定义一个常量,则需要重新定义使用它的所有函数,否则它可能会使用旧值

(反常量+c+5)
(除雾试验(x)
(+x+c+)
(测试1);==>6
(除霜常数+c+6)
(测试1);==>6或7
(除雾试验(x)
(+x+c+)
(测试1);==>7

对于初学者来说,符号、变量等可能有点令人惊讶。符号的功能令人惊讶。仅举几件事,您可以询问符号的符号值、符号包、符号名称、符号函数等。此外,符号可以声明关于它们的不同数量的信息,例如类型,这些信息提供了des建议编译可以利用它来创建更好的代码。所有符号都是如此,例如*,用于乘法的符号有一个执行乘法的符号函数。它还有一个符号值,即当前REPL中返回的最后一个值

关于符号的声明性信息的一个关键点是它们是否“特殊”。(是的,这是一个愚蠢的名字。)出于充分的理由,最好将所有全局符号声明为特殊符号。
defvar
defparameter
defconstant
都可以为您这样做。我们有一个约定,即所有特殊变量的前后都用*拼写,例如,
*标准输出*
。这种约定非常常见,因此som如果您忽略了遵循,e编译器将警告您

将符号声明为特殊符号的一个好处是,它将抑制在函数中拼写错误时收到的警告。例如
(defun faster(多少)(incf*speed*hw much))
将生成关于
hw much
的警告

与词法作用域相比,符号最酷的特性是它是通过我们称之为动态作用域的方式来管理的。回想一下*如何在REPL中具有最后一个结果的值。在某些实现中,您可以有多个REPL(每个REPL都在自己的线程中运行)每个线程都希望拥有自己的*、自己的
*标准输出*
,等等。这在Common Lisp中很容易实现;线程建立“动态范围”,并绑定应该是该REPL本地的特殊项

因此,是的,在您的示例中,您可以只使用
setf*small*
;但是如果您从未声明它是特殊的,那么您将收到有关编译器如何认为您拼写错误的警告。

函数只是:

(defun start-over ()
  (setf *small* 1)
  (setf *big* 100)
  (guess-my-number))
它已经声明为一个特殊的全局变量。无需在函数内部反复执行

您可以在函数中使用
DEFPARAMETER
,但这是一种糟糕的样式

DEFPARAMETER
用于声明全局特殊变量和它们的可选文档。一次。如果需要多次声明,则通常在重新加载整个文件或系统时执行。文件编译器还将其在顶级位置识别为动态绑定变量的特殊声明

例如:

文件1:

(defparameter *foo* 10)

(defun foo ()
  (let ((*foo* ...))
    ...))
文件2:

(defun foo-start ()
  (defparameter *foo* 10))

(defun foo ()
  (let ((*foo* ...))
    ...))
如果Lisp使用
compile File
编译文件1 fresh,编译器会识别
def参数
,在下面的
let
中,我们有一个动态绑定

如果Lisp使用
compile File
编译文件2 fresh,编译器将无法识别
defparameter
,并且在下面的
let
中,我们有一个词法绑定。如果我们再次编译它,从这个状态,我们将有一个动态绑定

这里是第1版