第六章 Common Lisp 指南-函数
内容
l 返回函数的函数
l 科里化函数
返回函数的函数
“怎么写一个返回函数的函数呢?”是一个典型的问题,在学习Scheme语言的人中(这些人没有接初到LISP)普通存在。在SCHEME中,它们习惯这样做:
==> (define (adder n) (lambda (x) (+ x n)))
adder
==> ((adder 3) 5)
8
==> (define (doubler f) (lambda (x) (f x x)))
doubler
==> ((doubler +) 4)
8
在COMMON LISP中,这在很容易完成,但语法和语义不同。{dy}步,创建一个返回函数的函数,它看起来非常像,但除了一些很小的句法规则。但更深层的机理却不一样:
* (defun adder (n)
(lambda (x) (+ x n)))
ADDER
上面的例子中,我们定义一个函数ADDER它返回一个函数的对象。为了创建这样一个类型,你不得不使用算符FUNCTION,并且将它应用到一个LAMBDA表达式。(FUNCTION form)也可以被略减为#’form.在上面的例子中,我们使用了一个简单的概念。如果不考虑这一点儿语法,我们可以将其这样来写:
* (defun adder (n)
#'(lambda (x) (+ x n)))
ADDER
or
* (defun adder (n)
(function (lambda (x) (+ x n))))
ADDER
不管我们如何写它,ADDER将返回一个函数,无论我们如何写它。但是我们不能像在SCHEME中一样使用它。
;;; continued from above
* (adder 3)
#<Interpreted Function "LAMBDA (N)" {485FFE81}>
* ((adder 3) 5)
In: (ADDER 3) 5
((ADDER 3) 5)
Error: Illegal function call.
这就是为什么:对于函数和变量,CL使用一个不同的名命空间。同样的符号,可代表不同的意义,这取决于它们被计算的窗体中的位置。
* (boundp 'foo)
NIL
* (fboundp 'foo)
NIL
* (defparameter foo 42)
FOO
* foo
42
* (boundp 'foo)
T
* (fboundp 'foo)
NIL
* (defun foo (x) (* x x))
FOO
* (fboundp 'foo)
T
* foo ;;; ***
42
* (foo 3) ;;; +++
9
* (foo foo)
1764
* (function foo)
#<Interpreted Function FOO {48523CC1}>
* #'foo
#<Interpreted Function FOO {48523CC1}>
* (let ((+ 3))
(+ + +))
6
简单一点儿说,在COMMON LISP中,你可以将其视为,每个符号在CL中有两个存储位置。一个是单元:与值相关联,将某个值绑定到符号上,你可以使用函数BOUNDP来检测符号是否绑定到值。你能可以通过SYMBOL-VALUE来获得其值。
另一种单元,通常与函数相联系,代表着函数的定义。此种情形,符号被称作fbound,你可以通过FBOUNDP来测试一个符号是否是FBOUND的。你可以通过SYBMOL-FUNCTION来获得函数的存储单元。
好了,如果一个符号被评估了,它将被视为值单元返回的一变量,如上面被为***的部分。
如一个复合窗体被评估,并且它的car部分为一符号变量,那么函数单元将被使用,如上面被标为+++的部分。
在COMMON LISP中,不像SCHEME,被评估的复合窗体不是一个任意的窗体。如果它不是一个符号,那未它将是一个LAMBDA表达式,类似于
(lambda lambda-list form*)
这说明我们上面的错误,它即不是一个符号也不是一个lambda表达式。那,你也许会问,我们如何使用函数体(上面的ADDER返回的),答案是:使用FUNCALL或者是APPLY:
;;; continued from above
* (funcall (adder 3) 5)
8
* (apply (adder 3) '(5))
8
* (defparameter *my-fun* (adder 3))
*MY-FUN*
* *my-fun*
#<Interpreted Function "LAMBDA (N)" {486468C9}>
* (funcall *my-fun* 5)
8
* (*my-fun* 5)
Warning: This function is undefined:
*MY-FUN*
注意了,上面返回的(ADDR 3)函数 体存储在*MY-FUN*值单元中。如果,我们想使用函数*MY-FUN*在复合窗体的CAR单元,我们不得不储存一些东西在它的函数单元里。
;;; continued from above
* (fboundp '*my-fun*)
NIL
* (setf (symbol-function '*my-fun*) (adder 3))
#<Interpreted Function "LAMBDA (N)" {4869FA19}>
* (fboundp '*my-fun*)
T
* (*my-fun* 5)
8
现在,我们准备定义DOUBLER:
* (defun doubler (f)
(lambda (x) (funcall f x x)))
DOUBLER
* (doubler #'+)
#<Interpreted Function "LAMBDA (F)" {48675791}>
* (doubler '+)
#<Interpreted Function "LAMBDA (F)" {486761B1}>
* (funcall (doubler #'+) 4)
8
* (funcall (doubler '+) 4)
8
* (defparameter *my-plus* '+)
*MY-PLUS*
* (funcall (doubler *my-plus*) 4)
8
* (defparameter *my-fun* (doubler '+))
*MY-FUN*
* (funcall *my-fun* 4)
8
语句FUNCALL即不是函数本身,如#‘+,也不是存储单元里的函数符号,‘+。
上面的讨论都非常简单,我们甚至没有提及到宏,特殊窗体,符号宏,自评估体,以及词法环境。
科里化函数
一个相关的概念是currying,如果你以前有过函数式语言,你可能会熟悉它。在上面的概念之上,我们下面的内容将会非常容易:
* (declaim (ftype (function (function &rest t) function) curry)
(inline curry))
NIL
* (defun curry (function &rest args)
(lambda (&rest more-args)
(apply function (append args more-args))))
CURRY
* (funcall (curry #'+ 3) 5)
8
* (funcall (curry #'+ 3) 6)
9
* (setf (symbol-function 'power-of-ten) (curry #'expt 10))
#
* (power-of-ten 3)
1000
DECLAIM语句只是对编译器的一个暗示,它能xxx的产生代码。