【问题标题】:clojure cons vs conj with lazy-seqclojure cons vs conj with lazy-seq
【发布时间】:2020-11-18 14:39:39
【问题描述】:

为什么 cons 在这种情况下对惰性序列有效,而 conj 却不行?

这行得通:

(defn compound-interest [p i]
   (cons p (lazy-seq (compound-interest (* p (+ 1 i)) i))))

这不会(它给出堆栈溢出异常):

(defn compound-interest2 [p i]
   (conj (lazy-seq (compound-interest2 (* p (+ 1 i)) i)) p))

【问题讨论】:

  • 对于其他来这里的人:我下面的回答非常详细(可能过于详细),可能会令人困惑。让我在这里重复一下要点:conj 的语义要求它根据集合类型改变其行为。这需要对集合对象使用(多态)方法调用。 LazySeq 通过委托其内部值来处理该方法调用,这需要实现该内部值。相反, cons 的语义不要求它调用集合上的任何方法;它只需要将其存储在Cons 对象的一个​​字段中。
  • 答案的最后三段特别有帮助。有些人可能想先阅读它们。

标签: clojure stack-overflow cons


【解决方案1】:

(conj collection item)item 添加到collection。为此,它需要实现collection。 (我将在下面解释原因。)所以递归调用会立即发生,而不是被延迟。

(cons item collection) 创建一个以item 开头的序列,然后是collection 中的所有内容。值得注意的是,它不需要实现collection。所以递归调用将被推迟(因为使用了lazy-seq),直到有人试图获取结果序列的尾部。

我将在内部解释这是如何工作的:

cons 实际上返回一个clojure.lang.Cons 对象,这就是惰性序列的组成部分。 conj 返回您传递给它的相同类型的集合(无论是列表、向量还是其他)。 conj 使用对集合本身的多态 Java 方法调用来执行此操作。 (见line 524 of clojure/src/jvm/clojure/lang/RT.java。)

当 Java 方法调用发生在 lazy-seq 返回的 clojure.lang.LazySeq 对象上时会发生什么? (ConsLazySeq 对象如何协同工作以形成惰性序列将在下面变得更加清晰。)查看line 98 of clojure/src/jvm/clojure/lang/LazySeq.java。请注意,它调用了一个名为seq 的方法。这就是实现LazySeq 价值的原因(详情请跳转至line 55)。

所以你可以说conj 需要确切地知道你通过了什么样的集合,但cons 不需要。 cons 只要求“集合”参数是 ISeq

请注意,Clojure 中的Cons 对象与其他 Lisps 中的“cons 单元格”不同——在大多数 Lisps 中,“cons”只是一个包含 2 个指向其他任意对象的指针的对象。所以你可以使用 cons 单元来构建树,等等。 Clojure Cons 将任意 Object 作为头部,将 ISeq 作为尾部。由于Cons 本身实现了ISeq,因此您可以从Cons 对象构建序列,但它们也可以指向向量或列表等。(请注意,Clojure 中的“列表”是一种特殊类型( PersistentList),并且不是Cons对象构建的。)clojure.lang.LazySeq实现了ISeq,所以它可以用作尾部(“cdr " 在 Lisps 中) 的 ConsLazySeq 包含对某些代码的引用,该代码评估到某种类型的ISeq,但在需要之前它实际上不会评估该代码,并且在它评估代码之后,它会缓存返回的ISeq 并委托给它。

...这一切都开始有意义了吗?你知道惰性序列是如何工作的吗?基本上,您从LazySeq 开始。当LazySeq 实现时,它的计算结果为Cons,它指向另一个LazySeq。当那个被实现时……你明白了。所以你会得到一个LazySeq 对象链,每个对象都持有(并委托给)Cons

关于 Clojure 中“conses”和“lists”的区别,“lists”(PersistentList 对象)包含一个缓存的“length”字段,因此它们可以在 O(1) 时间内响应 count。这在其他 Lisps 中不起作用,因为在大多数 Lisps 中,“列表”是可变的。但是在 Clojure 中它们是不可变的,因此缓存长度是可行的。

Cons Clojure 中的对象没有 有缓存长度——如果有,它们怎么能用来实现惰性(甚至无限)序列?如果您尝试获取Conscount,它只会在其尾部调用count,然后将结果加1。

【讨论】:

  • 你的意思是实现项目:)
  • @Ivan: 不——consconj 的参数顺序相反。
  • 是的,对不起,我以为他写了(conj p (lazy-seq (compound-interest2 (* p (+ 1 i)) i)))
  • 所以因为conj 适用于所有seq(不仅仅是列表),所以它笼统地假设集合必须实现(如果它是一个向量,这将是必需的)。理论上,如果集合是一个列表,conj 可能与cons 一样工作,对吧? (或者我错过了什么?)
  • 很好的答案。除此之外,它还为另一个问题提供了答案:如果 Clojure 已经有了 PersistentLists,而 Cons'es 中缺少一个长度字段,为什么还要有 Cons?
猜你喜欢
  • 2011-03-01
  • 1970-01-01
  • 2012-09-07
  • 2015-12-21
  • 1970-01-01
  • 1970-01-01
  • 2015-10-25
  • 1970-01-01
  • 2014-02-14
相关资源
最近更新 更多