Recursion 当flet在递归函数中时会发生什么?

Recursion 当flet在递归函数中时会发生什么?,recursion,common-lisp,Recursion,Common Lisp,我一直在尝试实现NFA,底部的代码是推导所有当前状态的ε闭包。 我想用递归的方式实现它,因为epsilon闭包是递归的。在当前的实现中,使用flet在主函数中定义了一个辅助函数,并且似乎每次递归都独立定义了一个辅助函数。我的理解正确吗?如果是这样,在不多次定义相同内容的情况下,实现此代码的最整洁的方法是什么 (defun eps-closure (states transition-rule) (flet ((trace-eps-onestep (states transition-rule

我一直在尝试实现NFA,底部的代码是推导所有当前状态的ε闭包。 我想用递归的方式实现它,因为epsilon闭包是递归的。在当前的实现中,使用flet在主函数中定义了一个辅助函数,并且似乎每次递归都独立定义了一个辅助函数。我的理解正确吗?如果是这样,在不多次定义相同内容的情况下,实现此代码的最整洁的方法是什么

(defun eps-closure (states transition-rule)
  (flet ((trace-eps-onestep (states transition-rule)
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (let ((next (trace-eps-onestep states transition-rule)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
          next))))
您可以将FLET放在DEFUN周围,这样它就不在全局范围内

(flet ((trace-eps-onestep (states transition-rule)
          (remove-duplicates
           (flatten
            (append
             states
             (mapcar
              (lambda (state) (transition-state state :eps transition-rule))
              states))))))
  (defun eps-closure (states transition-rule)
    (let ((next (trace-eps-onestep states transition-rule)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
        next))))
或者您可以像在原始代码中一样在本地定义它。无需向其传递参数,因为局部函数可以访问所有局部变量。在这种情况下,在每次递归中重新定义它是合理的,因为变量是不同的

(defun eps-closure (states transition-rule)
  (flet ((trace-eps-onestep ()
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (let ((next (trace-eps-onestep)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
          next))))
您可以将FLET放在DEFUN周围,这样它就不在全局范围内

(flet ((trace-eps-onestep (states transition-rule)
          (remove-duplicates
           (flatten
            (append
             states
             (mapcar
              (lambda (state) (transition-state state :eps transition-rule))
              states))))))
  (defun eps-closure (states transition-rule)
    (let ((next (trace-eps-onestep states transition-rule)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
        next))))
或者您可以像在原始代码中一样在本地定义它。无需向其传递参数,因为局部函数可以访问所有局部变量。在这种情况下,在每次递归中重新定义它是合理的,因为变量是不同的

(defun eps-closure (states transition-rule)
  (flet ((trace-eps-onestep ()
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (let ((next (trace-eps-onestep)))
      (if (set-difference next states)
          (eps-closure next transition-rule)
          next))))

在我看来,这很好。这是一个典型的局部词汇功能

似乎每次递归都会独立定义一个辅助函数

这在编译代码中并不重要,而且函数也不会被重新定义。没有创建函数对象,也没有对符号进行赋值。编译器甚至可能决定将其内联

对于s表达式使用解释器解释的代码,在每次迭代时执行FLET语句可能会有一些开销,但对于已编译的代码,这并不重要,因为编译通常提前完成一次

要使代码与函数更加模块化,有以下几种方法:

就像在您的示例中一样,定义一个局部函数。我甚至会保留这些参数,即使它们在词法范围内时可以省略。(可选)声明以内联本地函数。保留参数可以简化代码重构,并通过使参数显式化来记录函数的参数

将其定义为全局函数,并在稍后的调用中向其提供所有参数。这些函数通常被命名为辅助函数,如%trace eps onestep,使用%作为不应直接调用或类似调用的全局函数的前缀。有时这是首选,因为它使独立跟踪助手函数更容易。但有些实现也可以单独跟踪本地函数

全球FLET:避免

在DEFUN周围使用FLET并不好,因为它会使DEFUN表单成为非顶级的,并防止文件编译器在文件编译期间可移植地将其识别为全局函数定义

使用SBCL编译器的示例


正如您可以从生成的x86-64机器代码中看到的,没有进行重新定义。

对我来说,这看起来还行。这是一个典型的局部词汇功能

似乎每次递归都会独立定义一个辅助函数

这在编译代码中并不重要,而且函数也不会被重新定义。没有创建函数对象,也没有对符号进行赋值。编译器甚至可能决定将其内联

对于s表达式使用解释器解释的代码,在每次迭代时执行FLET语句可能会有一些开销,但对于已编译的代码,这并不重要,因为编译通常提前完成一次

要使代码与函数更加模块化,有以下几种方法:

就像在您的示例中一样,定义一个局部函数。我甚至会保留这些参数,即使它们在词法范围内时可以省略。(可选)声明以内联本地函数。保留参数可以简化代码重构,并通过使参数显式化来记录函数的参数

将其定义为全局函数,并在稍后的调用中向其提供所有参数。这些函数通常被命名为辅助函数,如%trace eps onestep,使用%作为不应直接调用或类似调用的全局函数的前缀。有时这是首选,因为它使独立跟踪助手函数更容易。但有些实现也可以单独跟踪本地函数

全球FLET:避免

在DEFUN周围使用FLET并不好,因为它会使DEFUN表单成为非顶级的,并防止文件编译器在文件编译期间可移植地将其识别为全局函数定义

使用SBCL编译器的示例

从生成的x86-64机器代码中可以看出,没有重新定义。

A 很明显,这样做的方法是在您想要的任何本地定义函数中定义尾部递归循环:

(defun eps-closure (initial-states transition-rule)
  (flet ((trace-eps-onestep (states)
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (labels ((eps-closure-loop (states)
               (let ((next (trace-eps-onestep states)))
                 (if (set-difference next states)
                     (eps-closure-loop states)
                   next))))
      (eps-closure-loop initial-states))))
现在完全清楚的是,跟踪eps onestep只有一个定义。请注意,我还利用这个机会从所有调用中删除了第二个参数,因为它始终是同一个对象,并且我已经重命名了参数,希望它更有意义

我喜欢这种内置大量局部函数的大全局定义技巧,因为这意味着从阅读代码中可以完全清楚地看出,它们是只供全局函数使用的辅助函数

在这种特殊情况下,trace-eps-onestep只从一个地方调用,实际上根本没有存在的理由。一个好的编译器可能会完全优化它,但我认为以下代码在任何情况下都更清晰:

(defun eps-closure (initial-states transition-rule)
  (labels ((eps-closure-loop (states)
             (let ((next (remove-duplicates
                          (flatten
                           (append
                            states
                            (mapcar
                             (lambda (state)
                               (transition-state state :eps transition-rule))
                             states))))))
               (if (set-difference next states)
                   (eps-closure-loop next)
                 next))))
    (eps-closure-loop initial-states)))
最后,这种尾部递归局部函数在CL中不是很自然,尽管我经常这样编程!:我认为,类似于以下的情况可以说更清楚:

(defun eps-closure (initial-states transition-rule)
  (loop for states = initial-states then next
        for next = (remove-duplicates
                    (flatten
                     (append
                      states
                      (mapcar
                       (lambda (state)
                         (transition-state state :eps transition-rule))
                       states))))
        if (null (set-difference next states))
        return next))

我没有测试所有这些函数都编译过的函数,但是缺少定义。

这样做的一个相当明显的方法是在您想要的任何本地定义函数中定义尾部递归循环:

(defun eps-closure (initial-states transition-rule)
  (flet ((trace-eps-onestep (states)
           (remove-duplicates
            (flatten
             (append
              states
              (mapcar
               (lambda (state) (transition-state state :eps transition-rule))
               states))))))
    (labels ((eps-closure-loop (states)
               (let ((next (trace-eps-onestep states)))
                 (if (set-difference next states)
                     (eps-closure-loop states)
                   next))))
      (eps-closure-loop initial-states))))
现在完全清楚的是,跟踪eps onestep只有一个定义。请注意,我还利用这个机会从所有调用中删除了第二个参数,因为它始终是同一个对象,并且我已经重命名了参数,希望它更有意义

我喜欢这种内置大量局部函数的大全局定义技巧,因为这意味着从阅读代码中可以完全清楚地看出,它们是只供全局函数使用的辅助函数

在这种特殊情况下,trace-eps-onestep只从一个地方调用,实际上根本没有存在的理由。一个好的编译器可能会完全优化它,但我认为以下代码在任何情况下都更清晰:

(defun eps-closure (initial-states transition-rule)
  (labels ((eps-closure-loop (states)
             (let ((next (remove-duplicates
                          (flatten
                           (append
                            states
                            (mapcar
                             (lambda (state)
                               (transition-state state :eps transition-rule))
                             states))))))
               (if (set-difference next states)
                   (eps-closure-loop next)
                 next))))
    (eps-closure-loop initial-states)))
最后,这种尾部递归局部函数在CL中不是很自然,尽管我经常这样编程!:我认为,类似于以下的情况可以说更清楚:

(defun eps-closure (initial-states transition-rule)
  (loop for states = initial-states then next
        for next = (remove-duplicates
                    (flatten
                     (append
                      states
                      (mapcar
                       (lambda (state)
                         (transition-state state :eps transition-rule))
                       states))))
        if (null (set-difference next states))
        return next))

我没有测试所有这些函数都编译过的任何函数,但是缺少定义。

只需将其定义为普通函数一次,而不是使用FLET。这样做,函数将处于全局范围。我可以避免吗?我发现flet里面的defun似乎能像我想的那样工作。这是一种常见的实现方式吗?只需将其定义为普通函数一次,而不是使用FLET。这样,函数将处于全局范围。我可以避免吗?我发现flet里面的defun似乎能像我想的那样工作。这是一种常见的实现方式吗?但是为什么会有人这样做呢?正如他在评论中所说的,他不希望helper函数位于全局范围内。为什么不将其保留在局部范围内呢?这就是局部函数经常用于的?因为它每次都重新定义函数,这是不必要的,因为它是同一个函数。函数每次都被重新定义?为什么编译器会这样做?但是为什么有人会这样做?正如他在评论中所说的,他不希望helper函数在全局范围内。为什么不把它留在局部范围内呢?这就是局部函数经常使用的地方?因为它每次都重新定义函数,这是不必要的,因为它是同一个函数。函数每次都被重新定义?为什么编译器会这样做?我认为defun总是在全局范围内定义函数,即使defun表单本身不是顶级表单。@exnihilo:是的,但文件编译器在编译期间不会知道eps闭包是全局函数。我认为defun总是在全局范围内定义函数,即使defun表单本身不是顶级表单。@exnihilo:它是,但文件编译器在编译期间不会知道eps闭包是全局函数。