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)声明的特殊变量