【问题标题】:Infinitely recursive lazy sequence appears as empty sequence in Clojure无限递归惰性序列在 Clojure 中显示为空序列
【发布时间】:2020-05-25 17:18:20
【问题描述】:

假设我写了:

(def stuff
  (lazy-seq stuff))

当我在 REPL 中询问 stuff 的值时,我希望它会陷入无限循环,因为我将 stuff 定义为它本身(这几乎没有说明这个序列) .

但是,我得到了一个空序列。

> stuff
()

为什么?


编辑:“递归”是指递归数据,而不是递归函数。

我仍然对序列终止的原因感到困惑。作为对比,下面的代码陷入了无限循环(并且炸毁了堆栈)。

(def stuff
  (lazy-seq (cons (first stuff) [])))

一些背景:这个问题源于我试图使用 Eratosthenes 的筛子来实现一个素数生成器。我的第一次尝试是:

(def primes
  (lazy-seq (cons 2
                  (remove (fn [x]
                            (let [ps (take-while #(< % x) primes)]
                              (some #(zero? (mod x %)) ps)))
                          (range 3 inf))))) ;; My customized range function that returns an infinite sequence

我认为它永远不会起作用,因为take-while 会继续要求更多的素数,即使它们还无法计算。所以当它工作得很好时,我感到很惊讶。

> (take 20 primes)
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)

【问题讨论】:

  • 您需要更具体。您可能希望阅读这些文档资源,尤其是 Getting Clojure 和 Brave Clojure:github.com/io-tupelo/clj-template#documentation
  • 它不能产生无限循环,因为那里没有函数调用。正如预期的那样会产生无限循环的是(defn stuff [] (lazy-seq (stuff)))
  • @leetwinski 可以产生一个只有惰性序列的无限循环。查看我的编辑。
  • take-while 受 x 的限制,并且 x 是当前素数“上限”,因此它永远不会要求比已经生成的素数更多的素数。另外range 已经产生了无限序列,无需自定义。 (drop 3 (range)),或(iterate inc 3)
  • @YizheSun 我研究了为什么@leetwinski 对primes 的定义有效,并在一篇博文中写道:phillippe.siclait.com/blog/primes-lazy-sequence

标签: clojure


【解决方案1】:

首先,每个惰性seq只能实现一次。其次,您对stuff 的定义不使用递归——stuff 不是函数。如果您查看lazy-seqdefinition,您可以看到您对stuff 的定义扩展为

(def stuff (new clojure.lang.LazySeq (fn* [] stuff)))

fn arg 到clojure.lang.LazySeq 构造函数被调用时,它返回已经实现的相同惰性序列。因此,当您尝试将惰性序列打印到 REPL 时,迭代会立即终止并返回 nil。

可以验证stuff的类型是clojure.lang.LazySeq

user=> (type stuff)
clojure.lang.LazySeq

在将stuff打印到REPL后,stuff就实现了

user=> (realized? stuff)
false
user=> stuff
()
user=> (realized? stuff)
true

你可以使用递归来得到你期望的效果

user=> (defn stuff
         []
         (lazy-seq (stuff)))
#'user/stuff
user=> (stuff) ;; Hangs forever.

【讨论】:

  • stuff 第一次实现是什么时候?价值是多少?另请参阅我的编辑。
  • stuff 是在您将符号发送到 REPL 时实现的,因为要打印 LazySeq,会调用其 seq 方法。 stuff 的值是clojure.lang.LazySeq 的一个实例。如果您查看 LazySeq 的 source,您可以跟踪逻辑。调用seq 后,Java 对象的所有字段都为空。它在 REPL 中显示为一个空序列,因为这是为实现 ISeq 的对象 x 定义 print-method 的方式,其中 (seq x) 为 nil。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多