Tail Call Optimization

尾递归最优是指当递归的中心条件满足时,编译器不以消耗内存栈的方式来优化递归调用.

前几天写的程序用到了递归:

(defn evaluate-reverse-polish-notation
  [tokens]
  (defn helper [tks stack]
      (if (empty? tks)
        ; base case
        (if (= (count stack) 1)
          (peek stack)
          (throw (Exception. (str "Broken notation: " (count stack)))))
        ; making progress
        (let [e (read-string (first tks))]
          (if (number? e)
            ; number: push into stack
            (helper
             (next tks)
             (conj stack (int e)))

            ; operator: apply to top 2 elems and then push into stack
            (helper (next tks)
                    (conj (pop (pop stack))
                          ((eval e) (peek stack)
                           (peek (pop stack)))))))))

想起来, Clojure 是不支持自动尾递归优化的. 在 Clojure 中, 要想做到尾递归优化, 是需要自己做些手脚的.

而且, 这个手脚做起来意外的简单:

diff --git a/core.clj b/core.clj
index b445084..320649d 100644
--- a/core.clj
+++ b/core.clj
@@ -45,12 +45,12 @@
         (let [e (read-string (first tks))]
           (if (number? e)
             ; number: push into stack
-            (helper
+            (recur
              (next tks)
              (conj stack (int e)))

             ; operator: apply to top 2 elems and then push into stack
-            (helper (next tks)
+            (recur (next tks)
                     (conj (pop (pop stack))
                           ((eval e) (peek stack)
                            (peek (pop stack)))))))))

需要注意的是, recur 只能用在 loop 或者 function 上. Clojure 做的事情是编译的时候在 loop / function 开始的地方设置 label, 在 recur 的地方使用 goto label. 也就是说, recur 实际上将尾递归变成了循环.