编辑:我在编写本文的过程中发现了对自己问题的部分答案,但我认为可以轻松对其进行改进,因此无论如何我都会将其发布。也许那里有更好的解决方案?

我正在寻找一种简单的方法来定义let形式的递归函数,而不必诉诸letfn。这可能是一个不合理的请求,但是我正在寻找这种技术的原因是因为我混合了数据和递归函数,它们相互依赖,因此需要大量嵌套的letletfn语句。

我想编写生成像这样的延迟序列的递归函数(以斐波那契序列为例):

(let [fibs (lazy-cat [0 1] (map + fibs (rest fibs)))]
  (take 10 fibs))


但是在Clojure中,似乎fibs在绑定期间不能使用它自己的符号。解决它的明显方法是使用letfn

(letfn [(fibo [] (lazy-cat [0 1] (map + (fibo) (rest (fibo)))))]
  (take 10 (fibo)))


但是,正如我之前所说,这导致许多麻烦的嵌套和letletfn的交替。

为了在不使用letfn且仅使用let的情况下执行此操作,我首先编写了一些使用我认为是U组合器的东西(今天刚刚听说过该概念):

(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宏,这里是a Gist of it。它的作用类似于Scheme的letrec(如果您碰巧知道的话),这意味着它是letletfn之间的交叉:您可以将一组名称绑定到相互递归的值,而无需将这些值函数(也可以使用惰性序列),只要可以评估每个项目的标题而无需参考其他项目(这就是Haskell或类型理论的说法;这里的“ head”可能代表例如惰性序列对象本身,(至关重要!!不涉及任何强制)。

您可以使用它来编写类似

(letrec [fibs (lazy-cat [0 1] (map + fibs (rest fibs)))]
  fibs)


通常只有在最高级别才有可能。有关更多示例,请参见要点。

正如问题文本中指出的那样,以上内容可以替换为

(letfn [(fibs [] (lazy-cat [0 1] (map + (fibs) (rest (fibs)))))]
  (fibs))


对于相同的结果在指数时间内; letrec版本具有线性复杂度(顶级形式也是如此)。

重复

自递归序列通常可以使用(def fibs (lazy-cat [0 1] (map + fibs (rest fibs))))构造-即,当固定范围的后视足以计算任何给定元素时。有关如何使用iterate计算clojure.contrib.lazy-seqs的示例,请参见fibs

clojure.contrib.seq

iterate提供了一个有趣的功能,称为c.c.seq,可实现以下功能

(take 10 (cseq/rec-seq fibs (map + fibs (rest fibs))))


它的局限性是只允许构造一个自递归序列,但是有可能从源头上提出一些实现思路,以实现更多不同的场景。如果您追求的是未在顶层定义的单个自递归序列,那么这必须是惯用的解决方案。

组合器

至于问题文本中显示的组合器,需要注意的是,它们受到JVM(以及Clojure)缺乏TCO(尾部调用优化)的阻碍,Clojure则选择直接将JVM的调用约定用于最高性能)。

顶层

还可以选择将相互递归的“事物”放在顶层,可能放在自己的名称空间中。如果需要以某种方式对那些“事物”进行参数化,则效果不佳,但是可以根据需要动态创建名称空间(有关实现思路,请参见rec-seq)。

最后评论

我会很容易地承认clojure.contrib.with-ns远不是习惯性的Clojure,并且如果有其他事情可以做的话,我会避免在生产代码中使用它(而且总是有顶级选项...)。但是,玩(IMO!)感觉很好,并且看起来效果很好。我个人很想知道没有letrec可以完成多少工作,以及letrec宏在多大程度上使事情变得更容易/更清洁...我还没有对此发表意见。所以,就在这里。再一次,对于单个自递归seq情况,letrec或contrib可能是最好的方法。

07-27 19:51