莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          詳解Clojure的遞歸(下)——相互遞歸和trampoline

          Posted on 2010-08-22 13:52 dennis 閱讀(3417) 評論(0)  編輯  收藏 所屬分類: Clojure
              詳解clojure遞歸(上)
              詳解clojure遞歸(下)
             
              這篇blog拖到現在才寫,如果再不寫,估計就只有上篇沒有下篇了,趁周末寫一下。

              上篇提到了Clojure僅支持有限的TCO,不支持間接的TCO,但是有一類特殊的尾遞歸clojure是支持,這就是相互遞歸。且看一個例子,定義兩個函數用于判斷奇數偶數:
          (declare my-odd? my-even?)
          (defn my
          -odd? [n]
                (
          if (= n 0)
                    false
                   (my
          -even? (dec n))))
          (defn my
          -even? [n]
                (
          if (= n 0)
                    true
                   (my
          -odd? (dec n))))

              這里使用declare做前向聲明,不然在定義my-odd?的時候my-even?還沒有定義,導致出錯。可以看到,my-odd?調用了my-even?,而my-even?調用了my-odd?,這是一個相互遞歸的定義:如果n為0,那肯定不是奇數,否則就判斷n-1是不是偶數,反之亦然。

              這個遞歸定義看起來巧妙,但是剛才已經提到clojure通過recur支持直接的TCO優化,這個相互遞歸在大數計算上會導致堆棧溢出:
          user=> (my-odd? 10000)
          java.lang.StackOverflowError (NO_SOURCE_FILE:
          0)

          user=> (my-even? 10000)
          java.lang.StackOverflowError (NO_SOURCE_FILE:0)


              怎么解決呢?一個辦法是轉化成一個直接遞歸調用,定義一個parity函數,當偶數的時候返回0,當奇數的時候返回1:
          (defn parity [n]
                (loop [n n par 
          0]
                      (
          if (= n 0)
                          par
                          (recur (dec n) (
          - 1 par)))))
          user=> (parity 3)
          1
          user=> (parity 100)
          0
          user=> (parity 10000)
          0
          user=> (parity 10001)
          1
             

              parity是直接的尾遞歸,并且使用了recur優化,重新定義my-odd和my-even就很簡單了:
          (defn my-even? [n] (= 0 (parity n)))
          (defn my
          -odd?  [n] (= 1 (parity n)))
             
              但是這樣的方式終究不是特別優雅,相互遞歸的定義簡潔優雅,有沒有什么辦法保持這個優雅的定義并且避免堆棧溢出呢?答案是trampoline,翻譯過來是彈簧床,查看trampoline函數文檔:
          user=> (doc trampoline)
          -------------------------
          clojure.core
          /trampoline
          ([f] [f 
          & args])
            trampoline can be used to convert algorithms requiring mutual
            recursion without stack consumption. Calls f with supplied args, 
          if
            any. If f returns a fn, calls that fn with no arguments, and
            continues to repeat, until the 
          return value is not a fn, then
            returns that non
          -fn value. Note that if you want to return a fn as a
            
          final value, you must wrap it in some data structure and unpack it
            after trampoline returns.
             簡單來說trampoline接收一個函數做參數并調用,如果結果為函數,那么就遞歸調用函數,如果不是,則返回結果,主要就是為了解決相互遞歸調用堆棧溢出的問題,查看trampoline的定義:
          (defn trampoline
            ([f]
               (let [ret (f)]
                 (
          if (fn? ret)
                    (recur ret)
                    ret)))

             看到trampoline利用recur做了尾遞歸優化,原有的my-odd?和my-even?需要做一個小改動,就可以利用trampoline來做遞歸優化了:
          (declare my-odd? my-even?)
          (defn my
          -odd? [n]
                (
          if (= n 0)
                    
          false
                   #(my
          -even? (dec n))))
          (defn my
          -even? [n]
                (
          if (= n 0)
                    
          true
                   #(my
          -odd? (dec n))))
           
             唯一的改動就是函數的末尾行前面加了個#,將遞歸調用修改成返回一個匿名函數了,使用trampoline調用my-even?和my-odd?不會再堆棧溢出了:
          user=> (trampoline my-odd? 10000000)
          false
          user
          => (trampoline my-even? 10000)  
          true
          user
          => (trampoline my-even? (* 1000 1000))
          true
            
             彈簧床這個名稱很形象,一次調用就是落到彈簧床上,如果是函數,反彈回去再來一次,如果不是,本次表演結束。
             
           
          主站蜘蛛池模板: 泸西县| 中方县| 永泰县| 东阿县| 莱芜市| 周至县| 滦平县| 淅川县| 伊吾县| 涞水县| 肥西县| 菏泽市| 龙海市| 诸城市| 滁州市| 将乐县| 沛县| 安新县| 涿州市| 和田县| 陆川县| 广昌县| 龙泉市| 桃园市| 原阳县| 平遥县| 化州市| 长治县| 金沙县| 阳曲县| 汝南县| 凤庆县| 井冈山市| 莎车县| 颍上县| 自治县| 乐亭县| 巴林左旗| 镇平县| 新蔡县| 定安县|