BnM*_*cGn 1 common-lisp lexical-scope
我正在寻找一种方便的方法,暂时交换功能.我知道我可以像这样手动设置一个功能符号:
CL-USER> (setf (symbol-function 'abcd) #'+)
#<FUNCTION +>
CL-USER> (abcd 1 2 4)
7
Run Code Online (Sandbox Code Playgroud)
我也知道labels
或者flet
可以临时为已定义的函数设置名称:
CL-USER> (labels ((abcd (&rest x)
(apply #'* x)))
(abcd 1 2 4))
8
Run Code Online (Sandbox Code Playgroud)
有没有办法手动,词法设置功能名称?例如.:
CL-USER> (some-variant-of-labels-or-let ((abcd #'*))
(abcd 1 2 4))
8
Run Code Online (Sandbox Code Playgroud)
注意:我尝试进入标签和flet的来源,但两者都是特殊的运算符.没有快乐.
您可以修改的绑定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))
Run Code Online (Sandbox Code Playgroud)
fflet和flabels都使用函数指示符(符号或函数),并使用它们和任何其他参数调用apply.因此你可以使用#'*
或'+
.
(fflet ((product #'*)
(sum '+))
(list (product 2 4)
(sum 3 4)))
;=> (8 7)
Run Code Online (Sandbox Code Playgroud)
这确实意味着你要引入开销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
Run Code Online (Sandbox Code Playgroud)
(flabels ((factorial (lambda (n &optional (acc 1))
(if (zerop n) acc
(factorial (1- n) (* acc n)))))) ; recursive
(factorial 7))
;=> 5040
Run Code Online (Sandbox Code Playgroud)
仔细研究了一段时间之后,我发现在Scheme fflet
中let
,由于Scheme是一个Lisp-1 ,因此是一样的.要获得行为flabels
,您必须letrec
在Scheme中使用.搜索letrec
Common Lisp的实现会产生一些有趣的结果.
Robert Smith的Let Lis for Common Lisp包含了这个描述和示例:
LETREC:LETREC是一个旨在模仿Scheme的letrec形式的宏.它是Common Lisp中一个有用的函数式编程结构,你可以在其中创建需要在功能上绑定到符号的函数.
Run Code Online (Sandbox Code Playgroud)(defun multiplier (n) (lambda (x) (* n x))) (letrec ((double (multiplier 2)) (triple (multiplier 3))) (double (triple 5))) ;= 30
当然,这与申请有同样的问题,并且说明包括
不幸的是,宏不是一个非常有效的实现.函数调用有一个间接层.基本上,LETREC具有绑定功能
Run Code Online (Sandbox Code Playgroud)(name fn)
被扩展为表单的LABELS绑定
Run Code Online (Sandbox Code Playgroud)(name (&rest args) (apply fn args))
这有点糟糕.
对于特定于实现的宏的实现方式,欢迎使用补丁.
在2005年,用户rhat在comp.lang.lisp上询问了一个与Scheme的letrec相当的Common Lisp,并且指向了标签.