调用本地声明的函数

时间:2013-11-17 02:49:47

标签: common-lisp

以下是来自 Lisp的土地(第138页或此处:http://landoflisp.com/wumpus.lisp)的一段代码:

(defun get-connected (node edge-list)
 (let ((visited nil))
    (labels ((traverse (node)
                (unless (member node visited)
                  (push node visited)
                  (mapc (lambda (edge)
                          (traverse (cdr edge)))
                        (direct-edges node edge-list)))))
      (traverse node))
   visited))

作为一个新手,我想知道第9行的(遍历节点)是否在“包装”函数中最终调用了这个“本地”定义的函数 get-connected 即可。也就是说,当调用 get-connected 时,它会“构造”遍历(第3 - 8行),然后在第9行调用/使用它。此外,本地变量已访问似乎被遍历推送命令打包,然后留在 let 块的后门口我猜,作为“回报价值?”当然,这必须是某种方式的返回值,“实时”并且可用于调用函数;否则,整个连接功能都没有用!

对我来说,一个Lisp /功能新手,这一切看起来都很奇怪。这是我听过的关闭野兽之一吗?如果是这样,我通过这样一个奇怪的构造函数获得了什么?我理解它的作用 - 它从图形顶点开始并遍历尽可能多的边缘,记住顶点,但它(除了漂亮的递归之外)是典型的Lisp /功能策略吗?

BTW,这是一个边缘列表的例子:

*test-edges* = ((2 . 8) (8 . 2) (4 . 5) (5 . 4) (6 . 2) (2 . 6) (3 . 10) (10 . 3) (7 . 5) 
(5 . 7) (7 . 10) (10 . 7) (4 . 5) (5 . 4) (9 . 3) (3 . 9) (3 . 5) (5 . 3)
(3 . 1) (1 . 3) (5 . 8) (8 . 5) (8 . 9) (9 . 8) (5 . 2) (2 . 5) (1 . 2)
(2 . 1))

节点只是这些数字中的一个,这里是1到10之间的整数。这里有一些输出:

CL-USER> (get-connected 1 *test-edges*)
(9 6 2 8 4 5 7 10 3 1)

1 个答案:

答案 0 :(得分:2)

让我们改进缩进:

(defun get-connected (node edge-list)
  (let ((visited nil))
    (labels ((traverse (node)
               (unless (member node visited)
                 (push node visited)
                 (mapc (lambda (edge)
                         (traverse (cdr edge)))
                       (direct-edges node edge-list)))))
      (traverse node))
    visited))

该功能并不奇怪。它基本上是一个设置一些变量的函数,定义一个局部自递归函数,然后调用一次本地函数。

闭包怎么样?闭包是一个带有环境的函数对象。我们构造函数对象的唯一地方是MAPC形式的lambda。否则,不会创建和传递/返回任何函数对象。传递给MAPC的函数对象是第一个参数,是一个闭包。

我们也可以把它写成两个全局函数。本地函数方法的优点是什么:

  • 本地函数名称不是全局已知的。由于我们只在本地使用它,因此没有必要将其全局化。

  • 我们不需要将所有信息传递给本地函数。它可以访问周围的变量。

  • 我们可以有一个共享的词汇变量,用于收集结果

我们可以把它写成两个全局函数。它可能看起来像这样:

现在,遍历是一个全局函数,我们需要扩展边缘列表和被访问节点列表的参数列表(到目前为止)。我们还需要构造一个返回值:访问过的节点。

(defun traverse (node edge-list visited)
  (if (member node visited)
      visited
    (progn
      (mapc (lambda (edge)
              (setf visited (traverse (cdr edge) edge-list (cons node visited))))
            (direct-edges node edge-list))
      (cons node (remove node visited)))))

您在函数式编程中看到的典型模式也可以通过某种形式的递归替换MAPC。

现在这是我们新的全球功能:

(defun get-connected-1 (node edge-list)
  (traverse node edge-list nil))

它传递变量并添加一个空的受访节点列表。

回到原来的功能:

(defun get-connected (node edge-list)
  (let ((visited nil))    ; a shared local variable
    (labels ((traverse (node)    ; a local self-recursive function
               (unless (member node visited)
                 (push node visited)   ; update the shared local variable
                 (mapc (lambda (edge)  ; loop over the direct edges
                         (traverse (cdr edge)))  ; traverse the connected
                       (direct-edges node edge-list)))))
      (traverse node))  ; call the local function from get-connected
    visited))           ; get-connected returns this as the return value

上面的局部遍历函数有什么不好?它可能导致某些较大图形上的堆栈溢出。人们需要将其重写为尾递归变体。

我们也可以摆脱本地功能,自己管理已访问和未访问过的节点列表:

(defun get-connected-2 (start-node edge-list)
  (let ((visited nil)
        (nodes-to-visit (list start-node)))
    (loop for node = (pop nodes-to-visit)
          while node do
          (unless (member node visited)
            (push node visited)
            (loop for (nil . to) in (direct-edges node edge-list)
                  do (pushnew to nodes-to-visit))))
    visited))