Lisp 何时使用defparameter而不是setf?
我目前正在阅读《LISP之地》(Land of LISP)一书,我刚刚读完第一章。在那里,有一个小程序编写的计算机猜测数字在1和100之间。其代码如下: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))) (
(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版