2015-05-23 44 views
0

我正在操作lisp中的樹。 我認爲功能參數是可變的。如何更新lisp中的節點

例如,我創建一個列表fs和一個函數添加一個元素到它沒有返回列表本身(我真的不想返回它)。

(defparameter fs '(1 2 3 4)) 

(defun addtolist (fs) 
    (append fs '(6)) 
) 

(print (addtolist fs)) 
;;(1 2 3 4 6) 
(print fs) 
;;(1 2 3 4) 

我不明白爲什麼列表沒有保留修改後。 任何想法?

+0

你說:'我認爲功能參數是可變的。'你爲什麼這麼想?然後你說'...創建一個列表fs':你沒有創建一個'fs'列表。你創建了一個變量'fs',然後有一個列表作爲它的值。 –

回答

1

並非所有函數都修改它們的參數;其實大部分(如append)都沒有。

我認爲你要找的是nconc

+0

那麼如何添加列表中的元素並保持修改而不返回列表? – anothertest

+0

謝謝。如果列表fs爲空,則不會保存該值。我該如何解決這個問題? – anothertest

+0

無法評論我看不到的代碼。 –

1

首先,ANSI Common Lisp的使用情況的說明:

(defparameter fs '(1 2 3 4)) 

這裏,defparameter介紹fs是一個動態變量。其中一個意思是,符號fs本身被標記爲「特殊」,這會影響其所有後續綁定。

(defun addtolist (fs) 
    (append fs '(6)) 
) 

其結果是,在這裏,函數參數fs並不像通常預期的一個詞法變量,但仍然是一個動態變量。這幾乎肯定是無意的,並且可以改變行爲。

這個問題,防止了命名紀律,最常見的一個是「耳罩公約」:前緣和後星號:

(defparameter *my-dynamic-variable* 42) 

注意如何在Common Lisp的標準動態變量命名爲這樣:*standard-output**print-base*,*random-state*,*package*和許多其他。

我不明白爲什麼列表沒有保留修改後。

這是因爲append函數自己計算一個新的列表,只留下舊的列表。 (新的清單可以使用舊的清單,但舊清單可以不受干擾)

在該特定情況下,可以使用破壞性版本append,即nconc函數。 nconc將重寫原始列表的尾部,使其指向(6)。但是,有兩個問題:

  1. 輸入列表是文字。根據ANSI Common Lisp規範修改文字是未定義的行爲。沒有辦法將文字'(1 2 3 4)修改爲(1 2 3 4 6)。文字對象必須被視爲只讀。

  2. 該方法不通用。即使我們在addtolist中使用nconc,並且確保我們不會輸入文字(爲了避免未定義的行爲),我們的addtolist將被打破。例如,它不能添加到一個空的列表!

在Lisp中,我們沒有「袋狀」列表,可以在沒有返回的情況下追加。空的列表由一個符號表示,符號nil,它不是一個沒有任何內容的容器。我們不能將nil更改爲非空列表。

在Lisp中通過在廣義位置(某些setf可選位置,如變量)中存儲列表來模擬「袋狀」列表。在更新列表時,我們將新版本分配回變量。

當對象必須包含關聯對象的列表時,該對象被封裝。例如,對於結構:

(defstruct notifier 
    (listeners))  ;; a notifier has listeners 

(defun add-listener (notifier listener) 
    (pushnew listener (notifier-listeners notifier))) 

(defun remove-listener (notifier listener) 
    (setf (notifier-listeners notifier) ;; candidate for define-modify-macro 
     (remove listener (notifier-listeners notifier)))) 

[1]> (defvar *n* (make-notifier)) 
*N* 
[2]> *n* 
#S(NOTIFIER :LISTENERS NIL) 
[3]> (add-listener *n* 4) 
(4) 
[4]> (add-listener *n* 5) 
(5 4) 
[5]> *n* 
#S(NOTIFIER :LISTENERS (5 4)) 
[6]> (add-listener *n* 6) 
(6 5 4) 
[7]> *n* 
#S(NOTIFIER :LISTENERS (6 5 4)) 
[8]> (remove-listener *n* 5) 
(6 4) 
[9]> *n* 
#S(NOTIFIER :LISTENERS (6 4)) 
[10]> (remove-listener *n* 6) 
(4) 
[11]> (remove-listener *n* 4) 
NIL 
[12]> *n* 
#S(NOTIFIER :LISTENERS NIL) 

「袋形」名單是一個巨大的劣勢,因爲沒有辦法使用,而不必在命令式編程參與。所有在這樣的列表上的操作都是破壞性的操作。對於Lisp中的列表使用類型,如生成和轉換深層嵌套的語法結構,這是不可接受的麻煩,低效和容易出錯的。

正如你在上面的例子中看到的,如果我們將一個功能列表「停放」到某個知名位置(比如一個結構的槽)中,我們可以很容易地獲得一個「袋狀」列表,插槽作爲「包」。破壞性包操作就像追加並刪除轉換成作業到那個位置。