为什么lisp宏推只改变符号?

时间:2017-08-31 03:34:42

标签: emacs macros lisp push

在许多lisp实现中,push是一个像这样的宏:

(push new list)
;; equal to 
(setf list (cons new list))

但是setf无法修改参数,例如:

(defun add-item (new list)
  (push new list))

不起作用,因为函数参数不是原始符号。

为什么不推动这样的工作:

(defun my-push (new list)
  (setcdr list (cons (car list)
                     (cdr list)))
  (setcar list new)
  list)

然后push可以在函数的参数上工作。 有没有理由让lisp推动这种方式工作?

我只是emacs lisp和sicp计划的新手。

4 个答案:

答案 0 :(得分:5)

破坏性push函数的一个问题是它无法在空列表nil上运行。这有点像“交易破坏者”。

请注意push宏,虽然它是一个命令式构造,它改变了保存列表头部的存储位置的值,但它避免了改变该列表的结构。

对于使用pushpop超过局部变量的列表处理代码,很容易理由;你不必担心变异列表结构可能导致的错误。

答案 1 :(得分:2)

请记住,多个符号(或其他值)可能指向该列表或其子列表。

如果push按您的建议方式工作,那么您可以更改超过指定符号的值。

考虑:

(setq l1 '(foo bar))
(setq l2 (append '(baz) l1))

如果你现在推动'通过操纵指向的cons小区的l1carcdr,您还可以修改l2的值。

当然,有时候这正是你想要做的事情;但是push不是这样做的,显然你不能重新定义push以这种方式工作而不会在其他代码中造成不必要的副作用。

答案 2 :(得分:1)

有几种类型的突变。一个是对象,您可以将carcdrcons更改为不同的对象。 cons之前和之后将具有相同的地址,因此指向它的每个变量都将指向相同的地址,但对象会发生变化。

(defparameter *mutating-obj* (list 1))
(defparameter *mutating-obj2* *mutating-obj*)
(setf (cdr *mutating-obj*) '(2))

在这里,您可以改变对象,以便两个变量仍然指向已更改的相同值。在评估其中任何一个时,您会看到(1 2)。 要知道,因为我们改变了对象,所以在开始时该值永远不会是(),因为它不是carcdr可以变异的东西。

在变量上使用setf,您可以将变量视为值的地址位置。因此setf将改变该位置而不是值本身。

(defparameter *var1* '(1 2 3))
(defparameter *var2* *var1*)

现在我们有两个指向同一列表的变量。如果我这样做:

(push 0 *var2*)

然后*var2*更改了指针,使其指向一个以0开头的新列表,并具有前一个值的尾部。这不会更改*var1*,它仍然指向之前的值*var2*

当你用一个值调用一个函数时,值被绑定为一个新变量,对它执行push会做同样的事情,改变那个变量,而不是碰巧指向同一个值的其他变量。

push的常见用法是从空列表开始并向其添加元素。设置carcdr不能将空列表更改为具有给定值的单个元素列表。指向nil的所有变量都不会更改,因此如果您的数据结构具有从未使用过的head元素,则使用rplacd(setf (cdr var) ...))的方法有效:

(defun make-stack ()
  (list 'stack-head))

(defun push-stack (element stack)
  (assert (eq (car stack) 'stack-head))
  (setf (cdr stack) (cons element (cdr stack)))
  stack)

(defun pop-stack (stack)
  (let ((popped (cadr stack)))
    (setf (cdr stack) (cddr stack))
    popped))

除非你专门设计它,否则这不起作用,因此push确实需要改变变量而不是值,因为它始终有效。 (除了认为它会改变价值观的初学者)

答案 3 :(得分:0)

谷歌之后

阅读更多代码, (如https://www.emacswiki.org/emacs/ListModification。) 我发现lisp程序员通常会复制并修改原始列表, 然后将其分配给原始列表符号。 也许这就是lisp的风格。

我来自javascript,它总是修改原始数组, 但很少复制它。 谢谢你的回答。