16.3 迭代式实用函数 (An Iteration Utility)

在这一节,我们先暂停一下编写 HTML 生成器的工作,转到编写迭代式例程的工作上来。

你可能会问,怎样才能知道,什么时候应该编写主程序,什么时候又应该编写子例程?

实际上,这个问题,没有答案。

通常情况下,你总是先开始写一个程序,然后发现需要写一个新的例程,于是你转而去编写新例程,完成它,接着再回过头去编写原来的程序。 时间关系,要在这里演示这个开始-完成-又再开始的过程是不太可能的,这里只展示这个迭代式例程的最终形态,需要注意的是,这个程序的编写并不如想象中的那么简单。 程序通常需要经历多次重写,才会变得简单。

  1. (defun map3 (fn lst)
  2. (labels ((rec (curr prev next left)
  3. (funcall fn curr prev next)
  4. (when left
  5. (rec (car left)
  6. curr
  7. (cadr left)
  8. (cdr left)))))
  9. (when lst
  10. (rec (car lst) nil (cadr lst) (cdr lst)))))

图 16.6 对树进行迭代

图 16.6 里定义的新例程是 mapc 的一个变种。它接受一个函数和一个列表作为参数,对于传入列表中的每个元素,它都会用三个参数来调用传入函数,分别是元素本身,前一个元素,以及后一个元素。(当没有前一个元素或者后一个元素时,使用 nil 代替。)

  1. > (map3 #'(lambda (&rest args) (princ args))
  2. '(a b c d))
  3. (A NIL B) (B A C) (C B D) (D C NIL)
  4. NIL

mapc 一样, map3 总是返回 nil 作为函数的返回值。需要这类例程的情况非常多。在下一个小节就会看到,这个例程是如何让每个页面都实现“前进一页”和“后退一页”功能的。

map3 的一个常见功能是,在列表的两个相邻元素之间进行某些处理:

  1. > (map3 #'(lambda (c p n)
  2. (princ c)
  3. (if n (princ " | ")))
  4. '(a b c d))
  5. A | B | C | D
  6. NIL

程序员经常会遇到上面的这类问题,但只要花些功夫,定义一些例程来处理它们,就能为后续工作节省不少时间。