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

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

问题描述:

在许多Lisp实现,推是一个宏观的这个样子的:为什么lisp宏推只改变符号?

(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) 

然后推可以在功能参数工作。 有没有任何理由让lisp推送这种方式工作?

我只是emacs lisp和sicp方案的新手。

您的破坏性push函数的一个问题是它不能在空列表nil上工作。这是一个“交易断路器”。

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

对于使用pushpop而不是局部变量的列表处理代码很容易推理;你不必关心由变异列表结构引起的可能的错误。

+0

零条件可以通过一些额外的测试来避免。 – gholk

+0

@gholk:如果你没有变量可访问性,怎么会这样呢? –

+0

@gholk问题是'nil'是一个你不能改变的原子。你不能写一个'mypush'函数,使得'(mypush 1 nil)'将'nil'变成'(1)'。该函数当然可以返回该对象,但调用者必须捕获该返回值并更新所有包含该列表的地方。 – Kaz

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

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

考虑:

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

如果你现在“推”到l1通过操纵car和它指向的,你也将修改的l2值的利弊细胞cdr

当然,有些时候这正是你想要做的;然而push不是实现它的方法,显然你不能重新定义push以这种方式工作而不会在其他代码中产生不需要的副作用。

谷歌后,读更多的代码, (如https://www.emacswiki.org/emacs/ListModification) 我发现口齿不清程序设计师通常复制并修改原始列表, 然后将其分配到原始列表符号。也许这是lisp的风格。

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

+0

如果您需要该行为,Lisp也有阵列 – coredump

+0

要么修改原始值是安全的,要么不是。该语言似乎不相关。 – phils

有几种类型的突变。一个是对象,您可以在其中将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变量将不会就此改变使用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真的需要改变变量,而不是从那以后它的价值永远。 (除了认为它改变数值的初学者)