簡體   English   中英

如何在Clojure中創建一個lazy-seq生成,匿名遞歸函數?

[英]How to create a lazy-seq generating, anonymous recursive function in Clojure?

編輯 :我在寫這篇文章的過程中發現了我自己問題的部分答案,但我認為它很容易改進,所以無論如何我都會發布它。 也許那里有更好的解決方案?

我正在尋找一種簡單的方法來定義let表單中的遞歸函數,而不需要使用letfn 這可能是一個不合理的請求,但我尋找這種技術的原因是因為我混合了數據和遞歸函數,這些函數在某種程度上相互依賴需要大量的嵌套letletfn語句。

我想編寫生成這樣的惰性序列的遞歸函數(以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)))

但正如我之前所說,這導致了許多繁瑣的嵌套和交替letletfn

為了做到這一點,沒有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功能的函數? 我無法想象我剛才所做的是慣用的......

letrec

我最近為Clojure寫了一個letrec宏,這里有一個要點 它就像Scheme的letrec (如果你碰巧知道的那樣),這意味着它是letletfn之間的交叉:你可以將一組名稱綁定到相互遞歸的值,而不需要將這些值作為函數(延遲序列是可以的)也是如此,只要有可能在不參考其他項目的情況下評估每個項目的頭部(那是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

clojure.contrib.seq

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM