Lisp 如何通过符号或字符串调用flet函数?

Lisp 如何通过符号或字符串调用flet函数?,lisp,common-lisp,Lisp,Common Lisp,如何通过符号或字符串调用flet函数 ((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1) 及 我知道上面的代码不起作用,因为funcall需要一个符号,但我不知道如何在flet或let中创建该符号。没有从符号到词法绑定函数的默认映射。CommonLisp没有提供这一点 我们需要做一些类似的事情: CL-USER 212 > ((lambda (s arg) (flet ((fn1 (x) x))

如何通过符号或字符串调用flet函数

((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1)


我知道上面的代码不起作用,因为funcall需要一个符号,但我不知道如何在flet或let中创建该符号。

没有从符号到词法绑定函数的默认映射。CommonLisp没有提供这一点

我们需要做一些类似的事情:

CL-USER 212 > ((lambda (s arg)
                 (flet ((fn1 (x) x))
                   (case s
                     (fn1 (fn1 arg)))))

               'fn1
               42)
42

没有从符号到词汇绑定函数的默认映射。CommonLisp没有提供这一点

我们需要做一些类似的事情:

CL-USER 212 > ((lambda (s arg)
                 (flet ((fn1 (x) x))
                   (case s
                     (fn1 (fn1 arg)))))

               'fn1
               42)
42

根据我的经验,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>
可以基于参数创建不同的闭包:

(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>

根据我的经验,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>
可以基于参数创建不同的闭包:

(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>
如何通过符号或字符串调用flet函数

((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1)
你不能;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;我们可能应该使用结构或对象

如何通过符号或字符串调用flet函数

((lambda (s) (flet ((fn1 (x) x)) (funcall s))) 'fn1)
你不能;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;我们可能应该使用结构或对象。

这个答案有两部分:

为什么你不能这样做; 如果你愿意,为什么你可以这样做。 在这两部分中,我都没有提到为什么做这种事情是糟糕的风格,因为它让你从外部提出关于函数实现细节的问题:除了函数在其实现中绑定的名称之外,任何人都不应该关心它

为什么你不能这么做 我认为值得思考的是,为什么你试图做的事情真的毫无意义。以下是带有缩进的代码,以便于阅读:

((lambda (s)
   (flet ((fn1 (x)
            x))
     (funcall s))
 'fn1)
这是在调用函数lambda s。。。带有符号fn1的参数。然后,函数尝试调用这个符号表示为函数的任何东西,您希望这应该是flet定义的函数

OK,让我们考虑类似的情况,但没有整个局部函数

((lambda (s)
   (let ((g 1))
     (??? s))
   'g))
因此,这里的目的是返回1,因为函数应该能够通过名称查找g的本地绑定值

这里有两个问题:

我应该给接线员打什么电话???是 如果它起作用,会有什么影响? 你应该做什么???是显然,在词汇范围的语言中,它不可能是一个全局函数。所以我们需要将它绑定为一个局部函数,这在CL中是有先例的:call next方法是这样的,或者它在语言中是一个新的特殊运算符:在任何一种情况下,我都会将它称为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)
这将使整个局势完全绝望。但这反过来又很可怕:任何人想要为这种语言编写一个编译器,并希望该编译器产生好的代码,几乎必须编写两个编译器:一个蹩脚的编译器用于出现vbv的代码,另一个好的编译器用于不出现vbv的代码。没有人愿意被迫这么做

这就是为什么vbv不存在的原因

但是局部函数是一样的:我可以发明另一个操作符,fbv,它获取某个符号的局部约束函数值,它有着与vbv相同的问题:

它不能是一个全局函数,特别是funcall或apply不能这样做,因为它们是全局函数; 它对使用它的代码性能的影响是可怕的。 如果你愿意,为什么你可以这样做 这就是Lisp:它是一种可编程编程语言。当然,如果你愿意,你可以这样做

下面是一个名为命名标签的宏的简单版本,它类似于标签,但保留名称。它很简单,因为它定义的本地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

请注意,查看命名标签的扩展将向您说明为什么默认情况下让系统为您执行此操作会很糟糕。

此答案有两部分:

为什么你不能这样做; 如果你愿意,为什么你可以这样做。 在这两部分中,我都没有提到为什么做这种事情是糟糕的风格,因为它让你从外部提出关于函数实现细节的问题:除了函数在其实现中绑定的名称之外,任何人都不应该关心它

为什么你不能这么做 我认为值得思考的是,为什么你试图做的事情真的毫无意义。以下是带有缩进的代码,以便于阅读:

((lambda (s)
   (flet ((fn1 (x)
            x))
     (funcall s))
 'fn1)
这是在调用函数lambda s。。。带有符号fn1的参数。然后,函数尝试调用这个符号表示为函数的任何东西,您希望这应该是flet定义的函数

OK,让我们考虑类似的情况,但没有整个局部函数

((lambda (s)
   (let ((g 1))
     (??? s))
   'g))
因此,这里的目的是返回1,因为函数应该能够通过名称查找g的本地绑定值

这里有两个问题:

我应该给接线员打什么电话???是 如果它起作用,会有什么影响? 你应该做什么???是显然,在词汇范围的语言中,它不可能是一个全局函数。所以我们需要将它绑定为一个局部函数,这在CL中是有先例的:call next方法是这样的,或者它在语言中是一个新的特殊运算符:在任何一种情况下,我都会将它称为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)
这将使整个局势完全绝望。但这反过来又很可怕:任何人想要为这种语言编写一个编译器,并希望该编译器产生好的代码,几乎必须编写两个编译器:一个蹩脚的编译器用于出现vbv的代码,另一个好的编译器用于不出现vbv的代码。没有人愿意被迫这么做

这就是为什么vbv不存在的原因

但是局部函数是一样的:我可以发明另一个操作符,fbv,它获取某个符号的局部约束函数值,它有着与vbv相同的问题:

它不能是一个全局函数,特别是funcall或apply不能这样做,因为它们是全局函数; 它对使用它的代码性能的影响是可怕的。 如果你愿意,为什么你可以这样做 这就是Lisp:它是一种可编程编程语言。当然,如果你愿意,你可以这样做

下面是一个名为命名标签的宏的简单版本,它类似于la bels,但保留名称。它很简单,因为它定义的本地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


请注意,查看命名标签的扩展将告诉您为什么默认情况下让系统为您这样做会很糟糕。

不是这样。XD如果我想避免的是使用条件函数。@PerduGames:否则无法检索词法函数。代码需要这样做。为什么?它们是如何产生的?不是那样的。XD如果我想避免的是使用条件函数。@PerduGames:否则无法检索词法函数。代码需要这样做。为什么?它们是如何创建的?你实际上是想在词汇范围内查找东西,还是不熟悉“?具体来说,如果你用“fn1”替换“fn1”或“fn1”,你的代码会工作吗?@DanRobertson:它不会工作,因为函数fn1不在flet的范围内…@RainerJoswig啊,谢谢。我没有仔细阅读代码,没有注意到奇怪的结构。你是真的想在词法范围内查找东西,还是不熟悉'?具体来说,如果你用“fn1”替换“fn1”或“fn1”,你的代码会工作吗?@DanRobertson:它不会工作,因为函数fn1不在flet的范围内…@RainerJoswig啊,谢谢。我没有仔细阅读代码,没有注意到奇怪的结构。很好,在问我之前,我不想对代码做任何事情,我只是喜欢在语言上玩得开心,看看我能去哪里。我对OOP的可能实现很感兴趣,有很多很好的方法,在问我之前,我不想对代码什么都不做,我只是喜欢对语言感兴趣,看看我能去哪里。我对OOP的可能实现很感兴趣,通过这种方式有很多方法。更简单的方法是:lambda s arg让l列出'fn1 lambdax funcall getf l s arg'fn17@PerduGames任何查找结构都将用于分派:哈希表、向量、关联列表、属性列表。不确定属性列表是否比向量简单。向量不会在任意符号上建立索引,但是它的直接访问可以被认为是简单的。我可能是错的,但是如果你开发代码直到回答这个问题,你就会明白为什么我说这里的plist比向量简单。这样做。更简单的方法是:lambda s arg让l列出'fn1 lambdax funcall getf l s arg'fn17@PerduGames任何查找结构都将用于分派:哈希表、向量、关联列表、属性列表。不确定属性列表是否比向量简单。向量不会在任意符号上建立索引,但是它的直接访问可以被认为是简单的。我可能错了,但是如果你开发代码直到回答这个问题,你就会明白为什么我说plist比vector简单。