Common Lisp 高阶函数学习笔记: function, funcall 和 apply...

孤街醉人 提交于 2019-12-07 21:49:58

Common Lisp 高阶函数学习笔记: function, funcall 和 apply 的用法

目录

0 概述

高阶函数是 Lisp 的一大特色, 所谓的高阶函数就是把一个函数当做另一个函数的参数来用, 如果把普通的函数调用看做是在二维平面上的活动, 那么高阶函数就相当于增加了一个维度:可以把高阶函数看做在三维立体空间的活动.

一般来说, 编程语言需要这种机制, 因为这样可以为开发者提供更灵活更高级的结构抽象能力, 正如<实用 Common Lisp 编程>中所说: 在 C 语言中使用函数指针, Perl 使用子例程引用, Python 跟 Lisp 一样, C# 使用代理, Java 则使用反射和匿名类.

高阶函数的一个应用场景是通用排序, 比如 Lisp 的函数 sort , 调用形式如下:

(sort '(6 2 5 3 7 0) #'>)

我们可以选择不同的比较函数(就是 #' 后面的 >), 这样的实现就比较灵活, 当我们想换一个比较函数, 如换成 < , 我们并不需要修改 sort 的代码, 只要新写一个 < 函数, 然后把它作为 sort 的参数传递进去就可以了.

另外回调函数和钩子也能够保存代码引用以便于以后运行.

1 函数 function 的用法

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> 

2 函数 funcall 的用法

既然我们得到了一个函数对象, 那么就可以调用它了, Lisp 提供了两个函数 funcall 和 apply 来调用一个函数对象, 它们两者的差别就在于参数.

当开发者明确知道需要传递给一个函数对象多少参数时, 可以使用 funcall , 它的语法如下:

(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 式的柱状图.

3 函数 apply 的用法

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!