首先,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)
。但是,有兩個問題:
輸入列表是文字。根據ANSI Common Lisp規範修改文字是未定義的行爲。沒有辦法將文字'(1 2 3 4)
修改爲(1 2 3 4 6)
。文字對象必須被視爲只讀。
該方法不通用。即使我們在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中的列表使用類型,如生成和轉換深層嵌套的語法結構,這是不可接受的麻煩,低效和容易出錯的。
正如你在上面的例子中看到的,如果我們將一個功能列表「停放」到某個知名位置(比如一個結構的槽)中,我們可以很容易地獲得一個「袋狀」列表,插槽作爲「包」。破壞性包操作就像追加並刪除轉換成作業到那個位置。
來源
2015-05-23 13:55:05
Kaz
你說:'我認爲功能參數是可變的。'你爲什麼這麼想?然後你說'...創建一個列表fs':你沒有創建一個'fs'列表。你創建了一個變量'fs',然後有一個列表作爲它的值。 –