Common Lisp/基礎主題/函式
函式是一個幾乎在每種程式語言中都會遇到的概念,但在 Lisp 中,函式尤其重要。從歷史上看,Lisp 受 lambda 演算 的啟發,其中每個物件都是一個函式。另一方面,在許多程式語言中,函式幾乎不是物件。Lisp 不是這樣:這裡的函式與其他物件具有相同的許可權,我們將在本章中討論它。
函式最常使用defun(DEfine FUNction)宏來建立。此宏接受一個引數列表和一個 Lisp 表示式序列,稱為函式的主體。defun 的典型用法如下
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(+ x1 x2))
這裡,引數列表是 (x1 x2),主體是 (print x1) (print x2) (+ x1 x2)。當呼叫函式時,主體中的每個表示式都會按順序(從第一個到最後一個)求值。如果發生了一些事情,我們想在到達最後一個表示式之前從函式中返回怎麼辦?return-from 宏允許我們做到這一點。例如
(defun print-arguments-and-return-sum (x1 x2)
(print x1)
(print x2)
(unless (and (numberp x1) (numberp x2))
(return-from print-arguments-and-return-sum "Error!"))
(+ x1 x2))
return-from 的第二個引數是可選的,這意味著它可以只使用一個引數呼叫 - 在這種情況下,函式將返回nil。但是等等:他們是怎麼做到的?我們的函式只接受兩個引數:不多也不少。答案是我們所說的“引數列表”並不像看起來那樣簡單。它實際上被稱為lambda 列表,而我們將在其他地方遇到它。稍後,我將解釋如何在函式中允許可選和關鍵字引數。
如本章開頭所述,Lisp 函式可以像任何其他物件一樣使用:它們可以儲存在變數中,作為引數傳遞給其他函式,並作為函式的值返回。在上一節中,我們定義了一個函式。該函式現在儲存在符號print-arguments-and-return-sum的函式單元中 - defun 將其放在那裡。但是,它並不永遠繫結到這個位置 - 我們可以將其提取出來,並放到另一個符號中,例如。為了訪問符號的函式單元,我們可以使用訪問器symbol-function。讓我們將我們的函式儲存在另一個符號中
(setf paars (symbol-function 'print-arguments-and-return-sum))
第一個反應是做這樣的事情
>(paars 1 1)
EVAL: undefined function PAARS
[Condition of type SYSTEM::SIMPLE-UNDEFINED-FUNCTION]
這是因為我們將函式放入了符號的值單元而不是其函式單元。很容易修復
(setf (symbol-function 'paars)
(symbol-function 'print-arguments-and-return-sum))
由於symbol-function 是一個訪問器,我們可以使用setf 與其一起使用。現在 (paars 1 1) 會產生它應該產生的結果。
雖然symbol-function 存在是有原因的,但它幾乎從未在真實程式碼中使用。這是因為它被 Lisp 的其他幾個特性所取代。其中之一是function 特殊運算子。它與symbol-function 特殊運算子的工作方式類似,只是它不會求值其引數,並且它返回當前繫結到符號的函式,這可能實際上不是其函式單元。(function foo) 也可以縮寫為 #',這極大地提高了它的實用性。另一方面,不可能寫
(setf (function paars) (function print-arguments-and-return-sum))
幸運的是,可以從除符號的函式單元以外的其他地方呼叫函式。函式設計器要麼是一個符號(在這種情況下使用其函式單元),要麼是函式本身。funcall 和apply 用於透過其函式設計器呼叫函式。請記住,符號paars 現在在其函式單元和值單元中包含相同的函式。讓我們改變它的值單元,以便差異顯而易見
(setf paars #'+)
現在讓我們以不同的方式呼叫它
(funcall paars 1 2) ;equivalent to (+ 1 2)
(funcall 'paars 1 2) ;equivalent to (funcall (symbol-function paars) 1 2)
(funcall #'paars 1 2) ;equivalent to (paars 1 2)
第二個和第三個例子之間的區別在於,如果paars 暫時繫結(使用flet 或labels)到其他函式,則第三個 funcall 將使用此臨時函式,而第二個 funcall 將仍然使用其函式單元。