[英]How to create a lazy-seq generating, anonymous recursive function in Clojure?
編輯 :我在寫這篇文章的過程中發現了我自己問題的部分答案,但我認為它很容易改進,所以無論如何我都會發布它。 也許那里有更好的解決方案?
我正在尋找一種簡單的方法來定義let
表單中的遞歸函數,而不需要使用letfn
。 這可能是一個不合理的請求,但我尋找這種技術的原因是因為我混合了數據和遞歸函數,這些函數在某種程度上相互依賴需要大量的嵌套let
和letfn
語句。
我想編寫生成這樣的惰性序列的遞歸函數(以Fibonacci序列為例):
(let [fibs (lazy-cat [0 1] (map + fibs (rest fibs)))]
(take 10 fibs))
但似乎在臨時中, fibs
在綁定時不能使用它自己的符號。 顯而易見的方法是使用letfn
(letfn [(fibo [] (lazy-cat [0 1] (map + (fibo) (rest (fibo)))))]
(take 10 (fibo)))
但正如我之前所說,這導致了許多繁瑣的嵌套和交替let
和letfn
。
為了做到這一點,沒有letfn
和使用let
,我開始寫一些使用我認為是U-combinator的東西(剛剛聽說過這個概念):
(let [fibs (fn [fi] (lazy-cat [0 1] (map + (fi fi) (rest (fi fi)))))]
(take 10 (fibs fibs)))
但是如何擺脫(fi fi)
的冗余?
正是在這一點上,經過一個小時的掙扎並逐漸向組合子Q添加位,我發現了自己問題的答案。
(let [Q (fn [r] ((fn [f] (f f)) (fn [y] (r (fn [] (y y))))))
fibs (Q (fn [fi] (lazy-cat [0 1] (map + (fi) (rest (fi))))))]
(take 10 fibs))
這個Q
組合器叫什么用於定義遞歸序列? 它看起來像沒有參數x
的Y組合子。 它是一樣的嗎?
(defn Y [r]
((fn [f] (f f))
(fn [y] (r (fn [x] ((y y) x))))))
在clojure.core或clojure.contrib中是否有另一個提供Y或Q功能的函數? 我無法想象我剛才所做的是慣用的......
我最近為Clojure寫了一個letrec
宏,這里有一個要點 。 它就像Scheme的letrec
(如果你碰巧知道的那樣),這意味着它是let
和letfn
之間的交叉:你可以將一組名稱綁定到相互遞歸的值,而不需要將這些值作為函數(延遲序列是可以的)也是如此,只要有可能在不參考其他項目的情況下評估每個項目的頭部(那是Haskell - 或者也許是類型理論 - 用語;這里的“head”可能代表懶惰的序列對象本身, - 至關重要! - 沒有強迫參與)。
你可以用它來寫東西
(letrec [fibs (lazy-cat [0 1] (map + fibs (rest fibs)))]
fibs)
這通常只能在頂級。 有關更多示例,請參閱Gist。
正如問題文本中所指出的,上面的內容可以替換為
(letfn [(fibs [] (lazy-cat [0 1] (map + (fibs) (rest (fibs)))))]
(fibs))
在指數時間內得到相同的結果; letrec
版本具有線性復雜度(頂層(def fibs (lazy-cat [0 1] (map + fibs (rest fibs))))
形式也是如此。
自遞歸seqs通常可以用iterate
構造 - 即當固定范圍的后視足以計算任何給定元素時。 有關如何使用iterate
計算fibs
的示例,請參閱clojure.contrib.lazy-seqs
。
ccseq
提供了一個名為rec-seq
的有趣函數,可以實現像
(take 10 (cseq/rec-seq fibs (map + fibs (rest fibs))))
它具有僅允許構建單個自遞歸序列的限制,但是可以從其源中提取一些實現想法以實現更多樣化的場景。 如果沒有在頂級定義的單個自遞歸序列是您所追求的,那么這必須是慣用的解決方案。
對於問題文本中顯示的組合器,重要的是要注意它們受到JVM上缺少TCO(尾調用優化)的阻礙(因此在Clojure中,它選擇直接使用JVM的調用約定)最佳表現)。
還可以選擇將相互遞歸的“事物”置於頂層,可能在它們自己的命名空間中。 如果這些“事物”需要以某種方式進行參數化,那么這種方法就無法工作,但如果需要,可以動態創建名稱空間(請參閱clojure.contrib.with-ns
了解實現方法)。
我很樂意承認, letrec
東西遠非慣用的Clojure而且我會避免在生產代碼中使用它,如果還有別的東西可以做(並且因為總有頂級選項......)。 但是,它(IMO!)很好玩,它似乎運作良好。 我個人有興趣了解在沒有letrec
情況下可以完成多少工作以及letrec
宏在多大程度上使事情變得更容易/更清潔...我還沒有對此形成意見。 所以,在這里。 再一次,對於單個自遞歸seq情況, iterate
或contrib可能是最好的方法。
fn
采用一個可選的name參數,該名稱綁定到其正文中的函數。 使用此功能,您可以將fibs
寫為:
(def fibs ((fn generator [a b] (lazy-seq (cons a (generator b (+ a b))))) 0 1))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.