【问题标题】:Return to top-level call of a recursive function in Lisp返回到 Lisp 中递归函数的顶层调用
【发布时间】:2014-12-05 04:15:14
【问题描述】:

我有一个递归函数,它需要递归直到找到某个结果。然而,在我第一次递归调用之后,在我的函数体中,我可能会进行一些其他计算或可能再次递归。但是,如果我递归并找到我正在寻找的结果,那么我想停止我一直在做的任何递归并返回该结果以避免进行不必要的计算。

在正常的递归调用中,一旦您到达“基本情况”,该情况将返回给调用的函数,然后将其返回给调用它的函数,依此类推。我想知道如何返回到第一次调用该函数,而不必为所有这些中间步骤返回一些东西。

对于我的基本递归,我可以编写如下函数:

(defun recurse (x)
   (if (= x 10)
       (return-from recurse x)
       (progn (recurse (+ x 1)) (print "Recursed!")))))
(recurse 1)

写它是为了说明我在递归调用后运行更多计算的函数的意思。而且,正如所写,这甚至不会返回我感兴趣的值,因为我在返回我关心的值之后进行了一些打印。 (注意:return-from 命令在这里是无关紧要的,因为我可以在它的位置写上“x”。在下面的第二个示例中,当我尝试返回顶级递归时,它只是为了绘制相似之处。)

现在,如果我想放弃所有那些额外的“递归!”我可以将所有内容都封装在一个块中,然后返回到该块:

编辑:这是我原始示例的函数包装器。这个例子现在应该更清楚了。

(defun recurse-to-top (start)
  (block top-level
    (labels ((recurse (x)
               (if (= x 10)
                   (return-from top-level x)
                   (progn (recurse (+ x 1)) (print "Recursed!")))))
      (recurse start))))

继续运行这个块,直到“找到”10,然后从顶层块返回,没有多余的打印,就像我想要的那样。但是,这似乎是获得此功能的一种非常笨拙的方式。我想知道是否有获得这种行为的标准或“最佳”方式。

【问题讨论】:

  • 在你的例子中,有一个块,“递归!” 从不打印出来,那为什么一开始就打印出来呢?我相信你的真实代码有更多的东西在你的例子中已经丢失了。
  • (defun recurse (x) (if (= x 10) x (recurse (+ x 1)))) 做你想做的事。
  • 你误解了我的问题。我的函数中的打印点是为了演示一个递归函数,它可能在递归调用之后执行某些操作。在第一个示例中,您看到每个打印函数都会运行,因为它们发生在每次递归返回之后。编写第二个示例是为了显示一种解决此问题的潜在方法,方法是一直返回到我的递归调用的顶层,因此不执行任何进一步的命令。我将 print 命令留在那里正是为了表明在该示例中它不会运行。
  • 但在第二个示例中,它从不执行,即使对于低于 10 的值也不执行,因此它本质上是死代码。为什么要跳过死代码?我认为你这里有一个 XY 问题,在这种情况下,更多的代码会更好。
  • 我认为您过于关注实际代码而不是它所代表的概念。在正常的递归调用中,一旦您到达返回给调用它的函数的“基本情况”,每次返回只会上升一个递归级别,然后在原始调用之前发生另一个返回。 (这是第一个示例。)一旦找到我的“基本情况”,我想知道如何从原始调用中一步返回。示例 2 显示了使用与第一个示例相同的函数来模拟此想法的(不好的)方法。

标签: recursion lisp common-lisp control-flow


【解决方案1】:

你想要某种非本地出口。有几个选择:return-fromgothrowsignal

也许这有一些变化?

(defun recurse (x &optional (tag 'done))
  (catch tag
    (when (= x 10)
      (throw 'done x))
    (recurse (1+ x) nil)
    (print "Cursed!")))

我相信它会做你想做的事,尽管可能会有很多不必要的捕捉。

与 Lisp 一样,您可以想象有一种完美的语言可以解决您的问题,并用该语言编写程序。例如。像

(defun recurse (x)
  (top-level-block recurse
    (when (= x 10)
      (return-from-top-level recurse x))
    (recurse (1+ x))
    (print "Cursed!")))

那么只需简单的编程即可实现新的宏top-level-blockreturn-from-top-level

不完善的示例代码如下:

(defmacro top-level-block (name &body body)
  `(if (boundp ',name)
       (progn ,@body)
       (catch ',name
         (let ((,name t))
           (declare (special ,name))
           ,@body))))

(defmacro return-from-top-level (name value)
  `(throw ',name ,value))

【讨论】:

    【解决方案2】:

    DEFUN 已经建立了一个词法块:

    (defun recurse (start)
      (labels ((recurse-aux (x)
                 (case x
                   (10 (return-from recurse x))
                   (15 x)
                   (otherwise
                     (recurse-aux (+ x 1))
                     (print "Recursed!")))))
        (recurse-aux start)))
    

    较旧的是使用CATCHTHROW,这是一个更动态的构造,因此允许跨函数退出:

    (defun recurse (start)
      (catch 'recurse-exit
        (recurse-aux start)))
    
    (defun recurse-aux  (x)
      (case x
        (10 (throw 'recurse-exit x))
        (15 x)
        (otherwise
         (recurse-aux (+ x 1))
         (print "Recursed!")))))
          (recurse-aux start))))
    

    正如 Lars 所提到的,还有更多方法可以像这样对控制流进行编程。

    【讨论】:

    • 第一个示例运行良好,但是由于您提到了多种方式,是否有特定的“最佳”或“首选”方式来获得此控制流?
    • @Nyles:第一个常见于嵌套函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-18
    • 1970-01-01
    相关资源
    最近更新 更多