我目前正在阅读这本书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))
到目前为止,我了解会发生什么,Using 'ash' in LISP to perform a binary search?在这方面给了我很多帮助。然而,还有一件事让我困惑:据我所知,你使用setf
为变量赋值,而defparameter
最初定义变量。我也理解defparameter
和defvar
之间的区别(至少我相信我做的; - ))。
现在我的问题是:如果我应该使用setf
为变量初始化后的值赋值,为什么start-over
函数使用defparameter
而不是{{} 1}?这有什么特别的原因,或者这只是邋??
答案 0 :(得分:3)
该功能只是:
(defun start-over ()
(setf *small* 1)
(setf *big* 100)
(guess-my-number))
它已被声明为一个特殊的全局变量。无需一次又一次地在函数内部进行。
你 CAN 在一个函数中使用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,则编译器会识别defparameter
,并且在以下let
中我们有动态绑定。
如果Lisp使用compile-file
编译文件2,编译器不会识别defparameter
,并且在下面的let
中我们有词法绑定。如果我们再次编译它,从这个状态,我们有一个动态绑定。
因此版本1更好,因为它更容易控制和理解。
在您的示例DEFPARAMETER
中出现多次,这是没用的。我可能会问,在哪里定义了变量,答案将指向多个源位置......
所以:确保您的程序元素主要定义为 ONCE - 除非您有充分的理由不这样做。
答案 1 :(得分:2)
通常,可以使用defvar
初始定义全局变量。 defvar
和defparameter
之间的差异很微妙,参见section in the CLHS这里起了作用:defparameter
(与defvar
相反)重新分配值,而defvar
会保留旧的绑定。
解决使用方法:通常,defvar
和朋友用作顶级表单,而不是某些函数(闭包是defun
上下文中最值得注意的例外)。我会使用setf
,而不是defparameter
。
答案 2 :(得分:2)
所以你有全局变量。这些可以由defconstant
定义(对于真正非特定的东西),defparameter
(可以更改的常量)和defvar
(如果你{{1}则不会覆盖的变量}}
您使用load
更改词汇以及全局变量的状态。 setf
可能已使用start-over
,因为代码并未真正定义它,而是更改它。如果您已将[{1}}替换为setf
defparameter
,则会停止工作。
defvar
start-over
与(defparameter *par* 5)
(setf *par* 6)
(defparameter *par* 5)
*par* ; ==> 5
(defvar *var* 5)
(setf *var* 6)
(defvar *var* 5)
*var* ; ==> 6
类似,只是一旦定义了CL实现可以在代码中自由内联。因此,如果重新定义常量,则需要重新定义使用它的所有函数,否则它可能使用旧值。
defconstant
答案 3 :(得分:0)
对于初学者符号,变量等可能有点令人惊讶。符号非常有特色。只是提一些事情,你可以问一个符号,它的符号值,符号包,符号名,符号功能等。此外,符号可以有不同数量的信息,例如,一个类型,它提供编译可能用来创建更好代码的建议。对于所有符号都是如此,例如*,用于乘法的符号具有执行该乘法的符号函数。它还有一个符号值,即当前REPL中返回的最后一个值。
关于符号的声明性信息的一个关键点是它们是否是特殊的。" (是的,它是一个愚蠢的名字。)有充分的理由,宣布所有全局符号都是特殊的,这是一种很好的做法。 defvar
,defparameter
和defconstant
为您做到了这一点。我们有一个约定,所有特殊变量在正面和背面拼写*,例如*standard-output*
。这个惯例很常见,如果你忽略了它,一些编译器会警告你。
将符号声明为特殊符号的一个好处是,它会抑制在函数中拼写变量时出现的警告。例如,(defun faster (how-much) (incf *speed* hw-much))
会生成关于hw-much
的警告。
特殊符号的最酷特征是它与我们所谓的动态范围一起管理,与词法范围形成对比。回想一下*如何*具有REPL中最后一个结果的值。在某些实现中,您可以拥有多个REPL(每个都在其中运行自己的线程),每个REPL都希望拥有它自己的*,它自己的*standard-output*
等。等等。这在Common Lisp中很容易;线程建立了一个动态范围"并绑定应该是该REPL本地的特殊内容。
所以是的,你可以在你的例子中setf *small*
;但是如果你从来没有声明它是特殊的那么你会得到关于编译器认为你拼写错误的警告。