comp
考慮一下Clojure 中這個簡單的遞回實作:
(defn my-comp
([f]
(fn [& args]
(apply f args)))
([f & funcs]
(fn [& args]
(f (apply (apply my-comp funcs) args)))))
有人告訴我,這樣做的正確方法是使用recur
,但我不確定如何recur
作業。特別是:有沒有辦法誘使上面的代碼recur
能夠實作?
uj5u.com熱心網友回復:
評價1
首先讓我們可視化問題。my-comp
正如問題中所寫的那樣,將創建一個深層的函式呼叫堆疊,每個都在堆疊上等待解決,阻塞直到最深的呼叫回傳-
((my-comp inc inc inc) 1)
((fn [& args]
(inc (apply (apply my-comp '(inc inc)) args))) 1)
(inc (apply (fn [& args]
(inc (apply (apply my-comp '(inc)) args))) '(1)))
(inc (inc (apply (apply my-comp '(inc)) '(1))))
(inc (inc (apply (fn [& args]
(apply inc args)) '(1))))
(inc (inc (apply inc '(1)))) ; ?? deep in the hole we go...
(inc (inc 2))
(inc 3)
4
尾遞回 my-comp
與其創建一長串函式,不如將其my-comp
重構為回傳一個函式,該函式在呼叫時loop
會在提供的輸入函式上運行 -
(defn my-comp [& fs]
(fn [init]
(loop [acc init [f & more] fs]
(if (nil? f)
acc
(recur (f acc) more))))) ; ?? tail recursion
((my-comp inc inc inc) 1)
;; 4
((apply my-comp (repeat 1000000 inc)) 1)
;; 1000001
評價2
使用和my-comp
重寫后,我們可以看到組合的線性迭代評估 -loop
recur
((my-comp inc inc inc) 1)
(loop 1 (list inc inc inc))
(loop 2 (list inc inc))
(loop 3 (list inc))
(loop 4 nil)
4
多個輸入引數
apply
您是否注意到本文開頭有十 (10) 個電話?這一切都是為了支持my-comp
序列中第一個函式的多個引數。my-comp
將這種復雜性與自身糾纏在一起是錯誤的。如果這是所需的行為,呼叫者可以控制執行此操作。
無需對重構進行任何額外更改my-comp
-
((my-comp #(apply * %) inc inc inc) '(3 4)) ; ? multiple input args
評估為 -
(loop '(3 4) (list #(apply * %) inc inc inc))
(loop 12 (list inc inc inc))
(loop 13 (list inc inc))
(loop 14 (list inc))
(loop 15 nil)
15
從右到左的順序
以上(my-comp a b c)
將a
首先應用,然后b
是,最后是c
。如果您想顛倒該順序,一個天真的解決方案是reverse
在loop
呼叫站點致電 -
(defn my-comp [& fs]
(fn [init]
(loop [acc init [f & more] (reverse fs)] ; ?? naive
(if (nil? f)
acc
(recur (f acc) more)))))
每次呼叫回傳的函式時,(reverse fs)
都會重新計算。為避免這種情況,請使用let
系結僅計算一次反轉 -
(defn my-comp [& fs]
(let [fs (reverse fs)] ; ? reverse once
(fn [init]
(loop [acc init [f & more] fs]
(if (nil? f)
acc
(recur (f acc) more))))))
uj5u.com熱心網友回復:
一種方法是重新排列此代碼以將一些中間函式傳遞回使用 recur 的定義。
該模型將是這樣的:
(my-comp #(* 10 %) - )
(my-comp (fn [& args] (#(* 10 %) (apply - args)))
)
(my-comp (fn [& args]
((fn [& args] (#(* 10 %) (apply - args)))
(apply args))))
最后一個 my-comp 將使用第一個 my-comp 多載(即(my-comp [f])
這是它的樣子:
(defn my-comp
([f] f)
([f & funcs]
(if (seq funcs)
(recur (fn [& args]
(f (apply (first funcs) args)))
(rest funcs))
(my-comp f))))
請注意,盡管不是可能的apply
目標,但recur
表單仍然可以接受作為序列傳遞的可變引數。
user> ((my-comp (partial repeat 3) #(* 10 %) - ) 1 2 3)
;;=> (-60 -60 -60)
但是請注意,實際上這個實作并不比你的更好:雖然recur
在創建函式時可以避免堆疊溢位,但它仍然會在應用程式上溢位(如果我錯了,請糾正我):
(apply my-comp (repeat 1000000 inc)) ;; ok
((apply my-comp (repeat 1000000 inc)) 1) ;; stack overflow
reduce
所以使用或其他東西可能會更好:
(defn my-comp-reduce [f & fs]
(let [[f & fs] (reverse (cons f fs))]
(fn [& args]
(reduce (fn [acc curr-f] (curr-f acc))
(apply f args)
fs))))
user> ((my-comp-reduce (partial repeat 3) #(* 10 %) - ) 1 2 3)
;;=> (-60 -60 -60)
user> ((apply my-comp-reduce (repeat 1000000 inc)) 1)
;;=> 1000001
uj5u.com熱心網友回復:
上面已經有了很好的答案,但我認為最初使用的建議recur
可能是在考慮更手動的結果積累。如果您還沒有看到它,reduce
這只是一個非常具體的用法loop/recur
:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn my-reduce
[step-fn init-val data-vec]
(loop [accum init-val
data data-vec]
(if (empty? data)
accum
(let [accum-next (step-fn accum (first data))
data-next (rest data)]
(recur accum-next data-next)))))
(dotest
(is= 10 (my-reduce 0 (range 5))) ; 0..4
(is= 120 (my-reduce * 1 (range 1 6))) ; 1..5 )
一般來說,可以有任意數量的回圈變數(而不僅僅是 2 像 reduce)。Usingloop/recur
為您提供了一種更“功能性”的回圈累積狀態的方式,而不是使用 and atom
and a doseq
or something。顧名思義,從外部看,效果非常類似于沒有任何堆疊大小限制的正常遞回(即尾呼叫優化)。
PS 如本例所示,我喜歡使用一個let
表單來非常明確地命名為下一次迭代生成的值。
PPS 雖然編譯器將允許您鍵入以下內容而不會混淆:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn my-reduce
[step-fn accum data]
(loop [accum accum
data data]
...))
重用變數名可能有點令人困惑和/或草率(尤其是對于 Clojure 或您的特定程式的新手)。
還
如果我沒有指出函式定義本身可以是一個recur
目標(即你不需要使用loop
),那我就失職了。考慮這個版本的階乘:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn fact-impl
[cum x]
(if (= x 1)
cum
(let [cum-next (* cum x)
x-next (dec x)]
(recur cum-next x-next))))
(defn fact [x] (fact-impl 1 x))
(dotest
(is= 6 (fact 3))
(is= 120 (fact 5)))
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/475130.html