高阶函数是 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>中对函数的讲解更详细深刻, 建议初学者认真阅读--我也在读. :)