Macros 通用Lisp,参考值和实际值
考虑这段代码:Macros 通用Lisp,参考值和实际值,macros,reference,common-lisp,Macros,Reference,Common Lisp,考虑这段代码: (defvar lst '(1 1)) (defmacro get-x (x lst) `(nth ,x ,lst)) (defun get-y (y lst) (nth y lst)) 现在让我们假设我想要更改列表中名为lst的元素的值,即带有get-x的car和带有get-y的cdr。 当我尝试使用get-x(使用setf)更改值时,一切正常,但如果我尝试使用get-y,则会发出错误信号(缩短): );警告: ; 未定义函数:(SETF GET-STUFF)
(defvar lst '(1 1))
(defmacro get-x (x lst)
`(nth ,x ,lst))
(defun get-y (y lst)
(nth y lst))
现在让我们假设我想要更改列表中名为lst的元素的值,即带有get-x的car和带有get-y的cdr。
当我尝试使用get-x(使用setf)更改值时,一切正常,但如果我尝试使用get-y,则会发出错误信号(缩短):
);警告:
; 未定义函数:(SETF GET-STUFF)
为什么会发生这种情况
我自己怀疑,之所以会发生这种情况,是因为宏只是展开,函数nth只是返回对列表中某个元素的值的引用,而函数另一方面对函数调用nth求值,并返回引用值的值(听起来很混乱)
我的怀疑正确吗?
如果我是正确的,那么人们如何知道什么仅仅是对一个值和一个实际值的引用呢?这个错误不会发生在宏版本中,因为,正如您所假设的,表达式
(setf(get-x some-x some list)some value)
将(在编译时)扩展成类似于(setf(nth some-x some list)的东西一些值)
(不是真的,但很复杂),编译器知道如何处理(即,有一个合适的setf
扩展器为函数nth
定义)
但是,在get-y
的情况下,编译器没有setf
扩展器,除非您提供一个。最简单的方法是
(defun (setf get-y) (new-value x ls) ; Note the function's name: setf get-y
(setf (nth x ls) new-value))
请注意,关于setf
-扩展器有一些约定:
setf
函数的第一个参数提供setf
函数都应该返回新值作为其结果(这就是整个setf
表单应该返回的内容)虽然有LISP方言有方言,但在普通LISP中没有“参考”概念(至少在C++意义上没有)。广义的地方形式(即,代码> SETF 及其机器)与普通C++风格的引用有很大的不同。如果您对细节感到好奇,请参阅CLHS 宏版本不会发生错误,因为正如您所假设的那样,表达式
(setf(get-x some-x some list)some value)
将(在编译时)扩展为类似(setf(nth some-x some list)some value)
(不太复杂),编译器知道如何处理它(即,为函数nth
定义了合适的setf
扩展器)
但是,在get-y
的情况下,编译器没有setf
扩展器,除非您提供一个。最简单的方法是
(defun (setf get-y) (new-value x ls) ; Note the function's name: setf get-y
(setf (nth x ls) new-value))
请注意,关于setf
-扩展器有一些约定:
setf
函数的第一个参数提供setf
函数都应该返回新值作为其结果(这就是整个setf
表单应该返回的内容)有,BTW,没有一般LISP中的“引用”(至少在C++意义上),尽管曾经有Lisp方言有位置。广义地方表(IE.<代码> SETF 及其机器)与普通C++风格的引用有很大不同。如果你对细节有兴趣,请参阅CLSS。
< P> SETF是宏。< /P> 其思想是,从数据结构中设置和读取元素需要两个操作,但通常需要两个不同的名称(或者甚至更复杂的名称)。SETF现在允许您仅使用一个名称:(get-something x)
上面读取了一个数据结构。反过来就是:
(setf (get-something x) :foobar)
上面用:FOOBAR设置X处的数据结构
SETF不把(get-something x)当作参考或类似的东西。它只是为每个操作建立了一个反向操作数据库。如果使用get-something,它知道反向操作是什么
SETF是怎么知道的?很简单:你必须告诉它
对于第n个操作,SETF知道如何设置第n个元素,它内置于commonlisp中
对于您自己的GET-Y操作,SETF没有这些信息。您必须告诉它。有关示例,请参阅常见的Lisp HyperSpec。一个示例是使用DEFUN和(SETF GET-Y)作为函数名
还请注意示例中的以下样式问题:
- lst不是DEFVAR变量的好名称。请使用*list*作为名称,以明确它是DEFVAR(或类似)声明的特殊变量
- “(1 2)是一个文字常量。如果您编写一个公共Lisp程序,更改它的效果是未定义的。如果您以后要更改列表,则应使用list或类似于COPY-list的内容将其设置为cons
- SETF是一个宏
其思想是,从数据结构中设置和读取元素需要两个操作,但通常需要两个不同的名称(或者甚至更复杂的名称)。SETF现在允许您仅使用一个名称:
(get-something x)
上面读取了一个数据结构。反过来就是:
(setf (get-something x) :foobar)
上面用:FOOBAR设置X处的数据结构
SETF不把(get-something x)当作参考或类似的东西。它只是为每个操作建立了一个反向操作数据库。如果使用get-something,它知道反向操作是什么
SETF是怎么知道的?很简单:你必须告诉它
对于第n个操作,SETF知道如何设置第n个元素,它内置于commonlisp中
对于您自己的GET-Y操作,SETF没有这些信息。您必须告诉它。有关示例,请参阅常见的Lisp HyperSpec。一个示例是使用DEFUN和(SETF GET-Y)作为函数名
还请注意示例中的以下样式问题:
- lst不是DEFVAR变量的好名称。请使用*list*作为名称,以明确它是由DEFVAR(o)声明的特殊变量