2010-07-24 71 views
33

我想知道爲什麼大多數的Common Lisp代碼,我看到有東西像爲什麼#在Common Lisp的lambda之前使用?

(mapcar #'(lambda (x) (* x x)) '(1 2 3))

,而不是僅僅

(mapcar (lambda (x) (* x x)) '(1 2 3))

這似乎正常工作。我開始學習Common Lisp,並且在Scheme中有一些背景,這讓我很感興趣。

編輯:我知道你需要#'與函數名稱,因爲他們住在不同的變量名稱空間。我的問題是關於lambda之前的#,因爲lambda已經返回一個函數對象(我認爲)。事實上,由於宏擴展而工作的事實只是使其更加耐人尋味......

回答

36

#'foo是對於(function foo)由讀者。

在CL,有幾種不同的命名空間,#'foo(function foo)將返回函數值的foo

您可能要search for "Lisp-1 vs. Lisp-2",檢查其他Stackoverflow questions,或爲了更多地瞭解多個命名空間的概念讀取old article py Pitman and Gabriel(也稱爲插槽細胞符號)。

,在拉姆達的#'的情況下,也可以省略其原因是,這是在CL的宏,其擴展了正是如此(從Hyperspec截取):

(lambda lambda-list [[declaration* | documentation]] form*) 
== (function (lambda lambda-list [[declaration* | documentation]] form*)) 
== #'(lambda lambda-list [[declaration* | documentation]] form*) 

#'仍然可以使用由於歷史原因(我認爲在Maclisp lambda中沒有擴展到函數形式),或者因爲有些人認爲,使用尖括號「標記」lambda可能會使代碼更具可讀性或連貫性。可能有一些特殊情況會導致這種差異,但總的來說,選擇哪種形式並不重要。

我猜你可以這樣想:(function (lambda ...))返回函數(lambda ...)創建。請注意,CL Hyperspec中的lambda同時有a macro AND a symbol entry。從後者:

lambda表達式是可以 來代替函數名的通過參照 的用於 某些上下文中通過直接描述其行爲 而不是間接表示一個函數 列表已建立功能的名稱。

functiondocumentation

如果名字是λ表達式,則 詞法閉合被返回。

我覺得差異也與調用拉姆達形式是這樣的:它被視爲一種形式((lambda ...) ...)進行評估,對(funcall #'(lambda ...) ...)。如果您想要了解更多關於這個主題的信息,請參閱c.l.l thread

從該線程的一些話:

(lambda (x) ...本身只是一些 不帶引號的表結構。這是它的 外觀作爲參數傳遞給 功能特殊形式(function (lambda (x) ...引起 函數對象存在

和:

它也通過這樣的事實加劇是 的LAMBDA宏是而後期 除了ANSI Common Lisp,所以所有 的真正老傢伙(即像我一樣) 瞭解到他們的lisp,當你需要 提供#'到lambda表達式 在映射函數中。否則 將調用不存在的lambda函數 。

宏的添加改變了這一點,但 我們有些人在我們的方式設置太 想改變。

+1

我知道命名空間的區別。但是我期望,因爲lambda直接返回一個函數對象(或者是它),所以不需要調用'function'或#'。爲什麼這樣? – 2010-07-24 15:16:25

+0

更新了答案。 – danlei 2010-07-24 15:53:01

+0

嗯,所以不,純lambda不返回一個函數對象...謝謝你的解釋。 – 2010-07-24 16:35:55

5

在大多數情況下最好避免#因爲它「大部分」是不必要的,並且使得代碼更加冗長。有些情況下需要引用某種形式的引用(請參見下面的示例4)。

注:本文中的所有示例都已在Emacs Lisp(GNU Emacs 25.2.1)中進行了測試,但它們應該在任何ANSI通用lisp中以相同的方式工作。兩種方言的基本概念是相同的。

簡單的解釋
首先,讓我們研究的情況下的時候,最好避免引用。函數是第一類對象(例如像任何其他對象一樣對待,包括將它們傳遞給函數並將它們分配給變量的能力),這些對象會評估自己。匿名函數(例如lambda表單)就是這樣一個例子。在Emacs Lisp(M-x ielm RET)或任何ANSI通用lisp上嘗試以下內容。

((lambda (x) (+ x 10)) 20) -> 30 

現在,嘗試引用的版本

(#'(lambda (x) (+ x 10)) 20) -> "function error" or "invalid function..." 

如果您使用堅持使用#」,你必須寫

(funcall #'(lambda (x) (+ x 10)) 20) -> 30 

詳細解釋
要真正當理解引用是必需的,必須知道Lisp如何評估表達式。請繼續閱讀。我保證做這個簡潔。

您需要了解Lisp的幾個基本事實:

  1. Lisp的 「永遠」 計算每一個表情。那麼,除非表達是引用的,在這種情況下,它將返回未評估。
  2. 原子評價自己。原子表達式不是列表。例子包括數字,字符串,哈希表和向量。
  3. 符號(變量名稱)存儲兩種類型的值。他們可以保持常規值和功能定義。因此,Lisp符號有兩個稱爲單元格的槽來存儲這兩種類型。非功能性內容通常保存在符號的值單元格中,並在函數單元格中運行。同時持有非功能性和功能性定義的能力將Emacs Lisp和Common Lisp放在2-Lisp類別中。在表達式中使用兩個單元格中的哪一個取決於如何使用該符號 - 更具體地說是它在列表中的位置。相比之下,Lisp的一些方言中的符號,Scheme是最有名的,它只能保存一個值。 Scheme沒有價值和功能細胞的概念。這樣的Lisp統稱爲1-Lisp。

現在,您需要大致瞭解Lisp如何評估S表達式(括號表達式)。每個S-表達大致如下評價:

  1. 如果被引用,返回它不計算
  2. 如果不帶引號的,得到它的CAR(例如,第一個元素),並使用以下規則評價它:

    一。如果原子只是返回它的值(例如3→3,「pablo」 - >「pablo」)
    b。如果是S表達式,則使用相同的整體程序對其進行評估。如果符號返回其功能單元的內容,則返回S表達式的CDR中的每個元素(例如,除列表的第一個元素外的所有元素)。

  3. 將從CAR獲得的函數應用於從CDR中每個元素獲得的值。

上面的過程意味着,在一個未加引號 S-表達CAR任何符號必須在其功能細胞有效的功能性定義。

現在,讓我們回到帖子開頭的例子。爲什麼

(#'(lambda (x) (+ x 10)) 20) 

產生錯誤?它會這樣做,因爲#'(lambda(x)(+ x 10))是S表達式的CAR,由於功能引用#',Lisp解釋器不對其進行評估。

#'(lambda (x) (+ x 10)) 

不是一個函數,而是

(lambda (x) (+ x 10)) 

是。請記住,引用的目的是爲了防止評估。另一方面,拉姆達形式評估自己,一個功能形式,它作爲一個有效的列表的CAR。當Lisp的評估的

((lambda (x) (+ x 10)) 20) 

的CAR它得到(拉姆達(X)(+×20)),其是可以在一個列表被應用到的參數的其餘部分的功能(提供的長度CDR等於lambda表達式允許的參數數量)。因此,

((lambda (x) (+ x 10)) 20) -> 30 

問題是什麼時候引用函數或符號來保存功能定義。除非你「不正確地」做事,否則幾乎沒有答案。通過「不正確」,我的意思是說,當你應該做相反的事時,你將一個功能定義放置在符號的值單元格或功能單元格中。請參見下面的示例來更好地理解:

例1 - 中值單元存儲功能
假設你需要使用「應用」與預計可變數量的參數的函數。一個這樣的例子是符號+。 Lisp將+視爲常規符號。功能定義存儲在+的功能單元中。你可以,如果你喜歡使用

(setq + "I am the plus function"). 

如果評估

+ -> "I am the plus function" 

但是值分配給它的值單元格,(1 + 2)仍正常工作。

(+ 1 2) -> 3 

函數apply在遞歸中非常有用。假設你想對列表中的所有元素求和。你不能寫

(+ '(1 2 3)) -> Wrong type... 

原因是+期望它的參數是函數。申請解決此問題

(apply #'+ '(1 2 3)) -> (+ 1 2 3) -> 6 

爲什麼我報價+上面?記住我上面概述的評估規則。 Lisp通過檢索存儲在其函數單元中的值來評估符號apply。它會得到一個可以應用於參數列表的函數過程。但是,如果我不引用+,Lisp將檢索存儲在其值單元中的值,因爲它不是S表達式中的第一個元素。因爲我們將+的值單元設置爲「我是加函數」,所以Lisp沒有得到+函數單元中保存的函數定義。實際上,如果我們沒有將它的值單元設置爲「我是加函數」,那麼Lisp將根據apply的要求檢索nil,這不是函數。

有沒有辦法使用+不帶引號的應用。就在這裏。你可以只評估了下面的代碼:

(setq + (symbol-function '+)) 
(apply + '(1 2 3)) 

這將計算爲6,預期因爲Lisp的評估板(應用+'(1 2 3)),現在發現+的存儲+功能定義的價值細胞。

例2 - 在值單元格中存儲功能定義
假設你存儲在符號的價值細胞的功能定義。這是實現如下:

(setq AFunc (lambda (x) (* 10 x))) 

評估

(AFunc 2) 

因爲Lisp語言不能找到AFunc的功能細胞的功能產生錯誤。通過使用funcall來解決這個問題,它告訴Lisp使用符號的值單元格中的值作爲功能定義。你用「funcall」來做到這一點。

(funcall AFunc 2) 

假設存儲在符號的值單元中的功能性定義是有效的,

(funcall AFunc 2) -> 20 

你可以通過使用FSET放置拉姆達形式在符號的功能細胞避免使用funcall具有:

(setf AFunc (lambda (x) (* 10 x))) 
(AFunc 2) 

此代碼塊將返回20,因爲lisp在AFunc的函數單元中找到了一個功能性定義。

例3 - 局部功能
假設你正在編寫一個函數,需要將無法在其他地方使用的功能。典型的解決方案是定義一個僅在主要範圍內有效的函數。嘗試:

(defun SquareNumberList (AListOfIntegers) 
    "A silly function with an uncessary 
    local function." 
    (let ((Square (lambda (ANumber) (* ANumber ANumber)))) 
    (mapcar Square AListOfIntegers) 
    ) 
) 

(SquareNumberList '(1 2 3)) 

此代碼塊將返回

(1 4 9) 

原因廣場未在上面的例子中引述的是,S形表達式根據我上面概述的規則評估。首先,Lisp提出了mapcar的功能定義。接下來,Lisp抽取第二個參數的內容(例如'Square)值單元格。最後,它返回'(1 2 3)未評估的第三個參數。

例4 - 一個符號價值的內容和功能的細胞
這裏是需要引號時,一個情況。

(setq ASymbol "Symbol's Value") 
(fset 'ASymbol (lambda() "Symbol's Function")) 
(progn 
    (print (format "Symbol's value -> %s" (symbol-value 'ASymbol))) 
    (print (format "Symbol's function -> %s" (symbol-function 'ASymbol))) 
)  

上面的代碼將評估爲

"Symbol's value -> Symbol's Value" 
"Symbol's function -> (lambda nil Symbol's Function)" 
nil 

報價需要在兩個

(fset 'ASymbol (lambda() "Symbol's Function")) 

(symbol-value 'ASymbol) 

(symbol-function 'ASymbol) 

因爲否則Lisp在每種情況下都會得到ASymbol的值,從而防止fset,符號值和符號函數正常工作。

我希望這篇冗長的文章對某人有用。