2017-08-31 67 views
1

在許多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方案的新手。

回答

5

您的破壞性push函數的一個問題是它不能在空列表nil上工作。這是一個「交易斷路器」。

請注意,push宏,雖然它是一個命令性的構造,它改變了保存列表頭部的存儲位置的值,但它避免了變換該列表的結構。

對於使用pushpop而不是局部變量的列表處理代碼很容易推理;你不必關心由變異列表結構引起的可能的錯誤。

+0

零條件可以通過一些額外的測試來避免。 – gholk

+0

@gholk:如果你沒有變量可訪問性,怎麼會這樣呢? –

+0

@gholk問題是'nil'是一個你不能改變的原子。你不能寫一個'mypush'函數,使得'(mypush 1 nil)'將'nil'變成'(1)'。該函數當然可以返回該對象,但調用者必須捕獲該返回值並更新所有包含該列表的地方。 – Kaz

2

請記住,符號(或其他值)可能指向該列表或其子列表。

如果push按照您的建議工作,那麼您可以更改超過指定符號的值。

考慮:

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

如果你現在「推」到l1通過操縱car和它指向的,你也將修改的l2值的利弊細胞cdr

當然,有些時候這正是你想要做的;然而push不是實現它的方法,顯然你不能重新定義push以這種方式工作而不會在其他代碼中產生不需要的副作用。

0

谷歌後,讀更多的代碼, (如https://www.emacswiki.org/emacs/ListModification) 我發現口齒不清程序設計師通常複製並修改原始列表, 然後將其分配到原始列表符號。也許這是lisp的風格。

我來自JavaScript,它總是修改原始數組, 但很少複製它。 感謝您的回答。

+0

如果您需要該行爲,Lisp也有陣列 – coredump

+0

要麼修改原始值是安全的,要麼不是。該語言似乎不相關。 – phils

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變量將不會就此改變使用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真的需要改變變量,而不是從那以後它的價值永遠。 (除了認爲它改變數值的初學者)