您可以修改的绑定symbol-function
不是词法绑定,因此这种选项并不真正适用。建立词法绑定函数的唯一方法是通过标签和 flet,因此您必须使用它们。也就是说,您可以使用宏轻松获得所需的语法:
(defmacro bind-functions (binder bindings body)
`(,binder ,(mapcar (lambda (binding)
(destructuring-bind (name function) binding
`(,name (&rest #1=#:args)
(apply ,function #1#))))
bindings)
,@body))
(defmacro fflet ((&rest bindings) &body body)
`(bind-functions flet ,bindings ,body))
(defmacro flabels ((&rest bindings) &body body)
`(bind-functions labels ,bindings ,body))
fflet 和 flabels 都采用函数指示符(符号或函数)并使用它们和任何附加参数调用 apply。因此,您可以使用#'*
or '+
。
(fflet ((product #'*)
(sum '+))
(list (product 2 4)
(sum 3 4)))
;=> (8 7)
这确实意味着您正在引入 的开销apply
,但尚不清楚您可以采取哪些措施来避免它。由于 lambda 表达式可以引用绑定的名称,我们可以允许这些引用指向新绑定的函数,或者指向外部的任何内容。这也是 flet 和标签之间的区别,这也是基于每个实现版本的原因:
(fflet ((double (lambda (x)
(format t "~&outer ~a" x)
(list x x))))
(fflet ((double (lambda (x)
(format t "~&inner ~a" x)
(double x)))) ; not recursive
(double 2)))
; inner 2
; outer 2
;=> 2 2
(flabels ((factorial (lambda (n &optional (acc 1))
(if (zerop n) acc
(factorial (1- n) (* acc n)))))) ; recursive
(factorial 7))
;=> 5040
备择方案
在考虑了一段时间后,我突然想到在 Scheme 中,fflet
与 相同let
,因为 Scheme 是 Lisp-1。要获得 的行为flabels
,您必须letrec
在 Scheme 中使用。搜索letrec
Common Lisp 的实现会发现一些有趣的结果。
Robert Smith 的Common Lisp 的 Letrec包括以下描述和示例:
LETREC:LETREC 是一个旨在模仿 Scheme 的 letrec 形式的宏。对于 Common Lisp 中的函数式编程来说,它是一个有用的构造,您可以在其中拥有需要在功能上绑定到符号的函数生成形式。
(defun multiplier (n)
(lambda (x) (* n x)))
(letrec ((double (multiplier 2))
(triple (multiplier 3)))
(double (triple 5)))
;= 30
当然,这与 apply 有相同的问题,并且注释包括
不幸的是,宏不是一个非常有效的实现。函数调用存在一定程度的间接性。本质上,带有绑定的 LETREC
(name fn)
扩展为表单的 LABELS 绑定
(name (&rest args)
(apply fn args))
这有点糟糕。
欢迎使用补丁来实现宏的特定实现方式。
2005 年,用户 rhat 在 comp.lang.lisp 上询问与 Scheme 的 letrec 等效的 Common Lisp,并被指向标签。