如何通过符号或字符串调用flet函数?
const test = {
a: "somestring",
b: 42
};
// Using for ... in
for (key in test) {
test[key] = false;
}
console.log(test);
和
((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1)
我知道上面的代码不起作用,因为funcall需要一个符号,但是我不知道如何在flet或let内部创建该符号。
答案 0 :(得分:3)
没有从符号到词法绑定函数的默认映射。 Common Lisp没有提供该功能。
一个人需要做类似的事情:
CL-USER 212 > ((lambda (s arg)
(flet ((fn1 (x) x))
(case s
(fn1 (fn1 arg)))))
'fn1
42)
42
答案 1 :(得分:3)
如何通过符号或字符串调用flet函数?
您不能; flet 函数是词汇绑定,而不是符号和函数之间的动态关联。 flet 函数绑定由符号命名,但是该关联不在全局环境中,并且在编译代码时可能会消失。
如果我们有这样的功能:
(lambda (s) (flet ((fn1 (x) x)) (funcall s)))
内部fn1
是隐藏的,封装的实现细节,外部人员不应该知道。
最好以这种方式对待它,并考虑不需要呼叫者了解fn1
的解决方案。
分派内部函数的匿名函数似乎是一种反模式;我们可以捕获多个lambda:
(let (v1 v2 ...)
(flet ((fn1 (x) ...) (fn2 (x) ...))
(vector #'fn1 #'fn2)))
现在,如果要调用fn2
,我们将返回返回的向量和(funcall (aref vec 1) arg)
。该向量本身将调度机制提供给共享相同捕获词法环境的多个操作。
我们正在做的是以一种不好的方式重塑OOP。我们应该使用结构或对象。
答案 2 :(得分:3)
此答案分为两个部分:
在这两个部分中,我都没有讨论过为什么做这种事情很糟糕,因为它可以让您从外部询问有关函数的实现细节的问题:除了函数之外,其他任何人都不应关注它在实现中绑定的名称。
我认为值得思考为什么您尝试做的事情实际上没有任何意义。这是带有缩进的代码,以使其更易于阅读:
((lambda (s)
(flet ((fn1 (x)
x))
(funcall s))
'fn1)
因此,这是在调用函数(lambda (s) ...)
,其参数为符号fn1
。然后,该函数尝试调用此符号表示的任何函数,并且您希望它应该是flet
定义的函数。
好的,让我们考虑类似的东西,但是没有整个本地函数
((lambda (s)
(let ((g 1))
(??? s))
'g))
所以,这里的意图是应该返回1
,因为该函数应该能够按名称为g
查找本地绑定的值。
好吧,这里有两个问题:
???
的接线员应该是什么? ???
应该是什么?好吧,很显然,它不能是词汇范围语言中的全局函数。因此,我们需要将其绑定为局部函数(CL中有此先例:call-next-method
就是这样),或者需要使其成为该语言中的新特殊运算符:无论哪种情况我将其称为vbv
(用于“可变绑定值”)。顺便说一下,CL中不存在该运算符。
如果vbv
存在,对语言的影响是什么?不好。例如,考虑以下功能:
(defun lookup (s)
(let* ((x 1)
(y (f x)))
(vbv s)))
有两件事很明显:此函数不知道s
是什么。因此,该功能受到两种方式的限制:
vbv
可以工作; x
的绑定无法编译。这意味着具有该运算符的语言的编译器在使用该运算符的情况下必然会很烂。唯一可以节省的地方是,由于vbv
不能是全局函数,因此可以在编译时告诉您是否必须执行所有这些额外的工作:您不能执行类似的操作
((lambda (f s)
(let ((x 1))
(funcall f s)))
#'vbv 'x)
这将使整个情况完全绝望。但这反过来太可怕了:任何想要为这种语言编写编译器,并希望该编译器产生良好代码的人,几乎都要编写两个 编译器:一个one脚的编译器用于代码vbv
出现的地方,一个好的地方却没有使用。没有人希望被迫这样做。
这就是vbv
不存在的原因。
但是局部函数是相同的:我可以发明另一个运算符fbv
,它得到某个符号的局部绑定函数值,并且具有与vbv
相同的所有问题:
funcall
或apply
不能这样做,因为它们是全局函数); 好吧,这就是Lisp:这是可编程编程语言。因此,当然,如果您愿意,可以这样做。
这是名为named-labels
的宏的简单版本,它类似于labels
但保留名称。这很简单,因为此定义的本地fbv
函数仅查看其自身的名称,因此嵌套此函数将无法正常工作。为了完成这项工作,我认为您可能需要一个代码遍历器。
(defun fbv (name)
;; function-binding-value, global version
(declare (ignore name))
(error "no bindings"))
(defmacro named-labels (fbindings &body code)
;; like LABELS but make FBV work in the body for things we've
;; defined.
(let ((stash-name (make-symbol "NAMED-LABELS-STASH")))
`(let ((,stash-name '()))
(labels ,fbindings
,@(loop for fb in fbindings
for n = (car fb)
collect `(push (cons ',n (function ,n)) ,stash-name))
(flet ((fbv (name)
(let ((found (assoc name ,stash-name)))
(if found
(cdr found)
(error "no binding for ~S" name)))))
,@code)))))
现在
> (funcall ((lambda (s)
(named-labels ((fn1 (x) x))
(fbv s)))
'fn1)
2)
2
请注意,查看named-labels
的扩展将向您展示为什么默认情况下让系统为您执行此操作会很麻烦。
答案 3 :(得分:2)
以我的经验,由flet
和labels
创建的内部函数的名称除了定义函数外,什么都不重要:
(defun create-adder (x)
(flet ((add (y) (+ x y)))
;; only this function CREATE-ADDER needs to know
;; that the flet is named ADD.
#'add))
(setq f (create-adder 3))
> #<Interpreted Closure (flet create-adder add) @ #x100208cf302>
将flet称为add
并不重要。
重要的是,它是可以funcall
进行的函数(关闭):
(funcall f 2)
> 5
您可以基于参数创建其他闭包:
(defun create-adder (kind)
(flet ((add-1 (x) (+ x 1))
(add-2 (x) (+ x 2)))
(ecase kind
(1 #'add-1)
(2 #'add-2))))
(create-adder 1)
> #<Interpreted Closure (flet create-adder add-1) @ #x10020903c52>
(create-adder 2)
> #<Interpreted Closure (flet create-adder add-2) @ #x10020902862>