Elisp 04:迭代

上一章:变量编程

迭代,亦称循环,表示一段重复运行的程序,其状态可在每次重复运行的过程当中发生变化。segmentfault

基于递归函数能够模拟迭代过程。例如如下程序函数

(defun princ\' (x)
  (princ x)
  (princ "\n"))

(defun current-line ()
  (buffer-substring (line-beginning-position) (line-end-position)))

(defun every-line ()
  (if (= (point) (point-max))
      (princ "")
    (progn 
      (princ\' (current-line))
      (forward-line 1)
      (every-line))))

(find-file "foo.md")
(every-line)

Elisp 解释器在上述程序最后一个表达式 (every-line) 求值时,会转而对 every-line 函数定义里的每一个表达式进行求值,可是当 Elisp 解释器在函数 every-line 的定义里又遇到了表达式 (every-line),致使它不得再也不次对 every-line 的定义里的每一个表达式进行求值。该过程周而复始,在每一次反复对 every-line 的定义进行求值时,princ\' 会不断输出当前文本行,而 forward-line 又不断将插入点移动到下一行的开头。因而,上述程序便解决了读取当前缓冲区内的每一行文本并输出于终端这个问题。优化

咱们活着,也是递归吧。如同咱们有寿命同样,Elisp 对函数的递归深度也有限度。every-line 这个函数,最多只能令 Elisp 解释器反复对其求值 max-lisp-eval-depth 次。`code

max-lisp-eval-depth 是 Elisp 解释器的全局变量,它定义了函数递归深度。使用递归

(princ\' max-lisp-eval-depth)

可查看它的值,在个人机器上,结果 800,这意味着上述的 every-line 函数只能令 Elisp 解释器反复对其求值 800 次。这也意味着,假若当前缓冲区内的文本行数超过 800 行时,every-line 函数的定义会令 Elisp 解释器因崩溃而终止工做。它的临终遗言是内存

Lisp nesting exceeds ‘max-lisp-eval-depth’

理论上,假若 Elisp 解释器可以对相似 every-line 这种尾部递归形式的函数予以优化,即可让本身用无休止地陷入对 every-line 的定义进行求值的过程当中。这种优化,叫做尾递归优化。get

不过,Elisp 解释器没有尾递归优化的功能,因此它必须提供迭代语法。Elisp 编程时最经常使用的迭代语法是 while 表达式,用法以下string

(while 逻辑表达式
  一段程序)

若逻辑表达式的求值结果为 t,Elisp 便会反复执行 while 表达式里的那段程序,不然,Elisp 解释器会将 nil 做为求值结果,结束对 while 表达式的求值。这意味着 while 表达式的求值结果要么是 nil,要么是 Elisp 解释器永无休止对其进行求值的过程。事实上,while 表达式的求值结果是什么,并不重要,重要的是它的内部如何表达程序运行状态的变化及程序的响应。it

基于 while 语法,可将上述 every-line 函数从新定义为

(defun every-line ()
  (while (< (point) (point-max))
    (princ\' (current-line))
    (forward-line 1)))

如今,再也不担忧因当前缓冲区行数过多的状况了,除非内存不够用,并且 every-line 的定义也更为简洁了。一举两得,可是假若我不先用递归函数模拟一下迭代过程,就很难有此刻愉悦的心情。

同理,上一章从新定义列表反转函数

(defun reverse-list (x)
  (let ((y '()))
    (defun reverse-list\' (x)
      (if (null x)
          y
        (progn
          (setq y (cons (car x) y))
          (reverse-list\' (cdr x)))))
    (reverse-list\' x)))

也能够改写为 while 版本:

(defun reverse-list (x)
  (let ((y '()))
    (while (not (null x))
      (setq y (cons (car x) y))
      (setq x (cdr x)))
    y))

其中,not 是 Elisp 的逻辑取反函数。须要注意的是,上述代码中,局部变量 y 出如今函数定义的最后,它就是函数的求值结果。由于,y 也是 S 表达式,Elisp 可对其进行求值。假若上述函数定义里的最后一行没有 y,那么 while 表达式的求值结果 nil 即是函数的求值结果。

有了迭代,那么递归函数还有必要再使用吗?

有必要。

一些树形结构的建立和遍历,例如二叉树或多叉树,用递归函数,不只天然,并且代码也很是简洁,再者一般也无需担忧递归深度的限制。以高度平衡二叉树为例,默认值为 800 的 max-lisp-eval-depth 足够了,由于叶结点数量高达 $2^{800}$ 的高度平衡二叉树几乎不可能具备现实意义。

最后记住一句话吧,迭代是线性的递归。

下一章:文本匹配

相关文章
相关标签/搜索