高階函數是 Lisp 的一大特點, 所謂的高階函數就是把一個函數當作另外一個函數的參數來用, 若是把普通的函數調用看作是在二維平面上的活動, 那麼高階函數就至關於增長了一個維度:能夠把高階函數看作在三維立體空間的活動.編程
通常來講, 編程語言須要這種機制, 由於這樣能夠爲開發者提供更靈活更高級的結構抽象能力, 正如<實用 Common Lisp 編程>中所說: 在 C 語言中使用函數指針, Perl 使用子例程引用, Python 跟 Lisp 同樣, C# 使用代理, Java 則使用反射和匿名類.app
高階函數的一個應用場景是通用排序, 好比 Lisp 的函數 sort , 調用形式以下:編程語言
(sort '(6 2 5 3 7 0) #'>)
咱們能夠選擇不一樣的比較函數(就是 #' 後面的 >), 這樣的實現就比較靈活, 當咱們想換一個比較函數, 如換成 < , 咱們並不須要修改 sort 的代碼, 只要新寫一個 < 函數, 而後把它做爲 sort 的參數傳遞進去就能夠了.函數
另外回調函數和鉤子也可以保存代碼引用以便於之後運行.oop
Lisp 中, 函數是一種對象, 所以有一個函數 function 能夠獲取一個函數對象:學習
CL-USER> (defun 乘以十 (x) (* 10 x)) 乘以十 CL-USER> (function 乘以十) #<Compiled-function 乘以十 #x302000DEF23F> CL-USER> (defun foo (x) (* 2 x)) FOO CL-USER> (function foo) #<Compiled-function FOO #x302000E2FC7F> CL-USER>
其中形如 #<...> 的形式就是函數對象的語法代理
函數 function 有一個簡略形式 #' (井號 單引號),用法以下:指針
CL-USER> #'foo #<Compiled-function FOO #x302000CCF46F> CL-USER> #'乘以十 #<Compiled-function 乘以十 #x302000CC64AF> CL-USER>
既然咱們獲得了一個函數對象, 那麼就能夠調用它了, Lisp 提供了兩個函數 funcall 和 apply 來調用一個函數對象, 它們二者的差異就在於參數.code
當開發者明確知道須要傳遞給一個函數對象多少參數時, 可使用 funcall , 它的語法以下:orm
(funcall 函數對象 參數1 參數2 參數3 … 參數n)
也就是說, funcall 中的第一個參數是函數對象, 從第二個直到最後一個參數, 都是函數對象的參數.
實際的代碼以下:
CL-USER> (funcall #'foo 3) 6 CL-USER>
那麼咱們試試能不能在 funcall 中直接使用函數對象:
CL-USER> (funcall #<Compiled-function FOO #x302000CCF46F> 3) 6 CL-USER> (funcall #'乘以十 3) 30 CL-USER> (funcall #<Compiled-function 乘以十 #x302000CC64AF> 34) 340
很好, 試驗結果代表:做爲一個函數對象,能夠被 funcall 直接使用.
這樣經過 funcall 使用函數好像跟直接調用函數沒有太大差異, 其實更常見的用法是把某個函數當作參數傳遞到另外一段代碼內, 在這段代碼內部經過 funcal 來調用該函數, 例如:
CL-USER> (defun 畫圖 (函數1 最小值 最大值 步長) (loop for i from 最小值 to 最大值 by 步長 do (loop repeat (funcall 函數1 i) do (format t "*")) (format t "~%"))) 畫圖 CL-USER> (畫圖 #'exp 0 4 1/4) * * * ** ** *** **** ***** ******* ********* ************ *************** ******************** ************************* ********************************* ****************************************** ****************************************************** NIL CL-USER> 注意這條語句 (畫圖 #'exp 0 4 1/4) , 說明最終傳遞進去的是一個函數對象.
也就是說咱們也能夠這麼作--直接把函數對象傳遞進去:
CL-USER> #'exp #<Compiled-function EXP #x300000098BAF> CL-USER> (畫圖 #<Compiled-function EXP #x300000098BAF> 0 4 1/4) * * * ** ** *** **** ***** ******* ********* ************ *************** ******************** ************************* ********************************* ****************************************** ****************************************************** NIL CL-USER>
這段代碼中的函數"畫圖"接受一個指數函數對象 exp 爲實參, 使用該指數函數在"最小值"和"最大值"之間以"步長"用星號繪製了一個 ASCII 式的柱狀圖.
接着是函數 apply , apply 能夠容許開發者把函數對象的參數收集到一個列表中. apply 的語法以下:
(apply 函數對象 (參數1 參數2 參數3 … 參數n))
也能夠寫爲:
(apply 函數對象 參數1 (參數2 參數3 … 參數n))
或者
(apply 函數對象 參數1 參數2 (參數3 … 參數n))
也就是說:它的第一個參數必須是函數對象, 最後一個參數必須是一個列表, 在函數對象和列表之間, 能夠有多個單獨的參數.
以上面的"畫圖"函數爲例, 它有4個參數, 第一個是函數對象, 後面三個都是函數對象的參數, 若是使用 apply 咱們能夠把它們寫成一個列表的形式:
(最小值 最大值 步長)
好比咱們能夠這麼調用:
CL-USER> (funcall #'畫圖 #'exp 0 4 1/4) * * * ** ** *** **** ***** ******* ********* ************ *************** ******************** ************************* ********************************* ****************************************** ****************************************************** NIL CL-USER> (defvar *畫圖數據* '(0 4 1/4)) *畫圖數據* CL-USER> (apply #'畫圖 #'exp *畫圖數據*) * * * ** ** *** **** ***** ******* ********* ************ *************** ******************** ************************* ********************************* ****************************************** ****************************************************** NIL CL-USER>
固然, 你也能夠試試這條語句:
CL-USER> (defvar *畫圖的所有參數* '(exp 0 4 1/4)) *畫圖的所有參數* CL-USER>
實踐發現, 這個參數不被接受, 也就是說 apply 的列表參數中不能把函數對象放在列表的第一個位置, 其餘位置是否可行, 我沒試過, 感興趣的朋友能夠本身試試.
最後補充一下: <On Lisp>中對函數的講解更詳細深刻, 建議初學者認真閱讀--我也在讀. :)