如何在Clojure中将惰性序列转换为非惰性序列

105

我在Clojure中尝试了以下代码,期望返回非惰性序列的类:

(.getClass (doall (take 3 (repeatedly rand))))

然而,这仍然返回 clojure.lang.LazySeq。我的猜测是 doall 确实会评估整个序列,但由于原始序列对于备忘仍然有用,因此它返回原始序列。

那么,从惰性序列创建非惰性序列的惯用方法是什么?


我很惊讶没有人问你为什么关心 doall 返回值的实际类型。 - tar
1
你可以将其转换为向量:(vec (take 3 (repeatedly rand))) - Kris
5个回答

181

doall是你需要的全部。仅仅因为序列具有LazySeq类型并不意味着它具有待定的评估。懒惰序列会缓存其结果,因此您只需走一遍懒惰序列(就像doall一样)即可强制执行所有内容,从而使其非懒惰。 seq不会强制求值整个集合。


2
我已将此更改为被接受的答案。另外,你如何确定LazySeq之前是否已经被评估过? - Tim Clemons
11
我认为你只需要调用 realized? - toofarsideways
2
可能应该有一个realize操作来匹配realized? - Reut Sharabani
1
这一切都很好。但是,由于某些函数(如contains?)无论您是否实现了惰性序列都不会受到影响,因此这回答了特定问题的提问,但对问题的标题则不太相关。 - matanster

85

这在某种程度上是一个分类问题。懒惰序列只是序列的一种类型,就像列表、向量或映射表一样。因此答案当然是:"取决于你想要获取哪种非懒惰序列:
可以从以下选项中选择:

  • 一个已完全求值的懒惰序列 (doall ...)
  • 用于顺序访问的列表 (apply list (my-lazy-seq)) 或 (into () ...)
  • 用于稍后随机访问的向量 (vec (my-lazy-seq))
  • 如果有特殊目的,则使用映射表或集合。

你可以按照需求选择最适合你的序列类型。


这是最好的答案。 - Felipe
8
被接受的答案在技术上是正确的,但这个答案对我来说最有用。我试图在向量上映射一个函数,然后将结果输出到一个文件中,在调用doall之后,文件中仍包含“clojure.lang.LazySeq@address”,而不是序列的内容。在值映射上调用vec得到了我需要输出到文件的内容。 - Jesse Rosalia
2
@JesseRosalia 很高兴知道 SO 上唯一的 Rich Hickey 回答是技术上正确的。;-) - Phil Cooper
1
(vec (my-lazy-seq)) is not so nice in situations like the following: (vec (json/parse-string "{\"foo\":\"bar\"}")) ;; => [["foo" "bar"]] Since cheshire chooses to produce a lazy-seq from (json/parse-string) - codeasone
对上述问题的缓解是使用急切的(json/parse-string-strict) - codeasone

24
这个有钱的人似乎懂clojure,而且完全正确。但我认为,使用你的例子,这个代码片段可能是对这个问题的一个有用的补充:
=> (realized? (take 3 (repeatedly rand))) 
false
=> (realized? (doall (take 3 (repeatedly rand)))) 
true

实际上,类型并没有改变,但是实现方式已经改变了。


3
值得注意的是,你不需要强制整个realized?序列都返回true。例如,(let [r (range) r? (realized? r)] (doall (take 1 r)) [r? (realized? r)]) => [false true] - Alex Coventry
28
这个有钱的家伙:D 哈哈 - Julian Krispel-Samsel
15
@nimrod :) 然而这个双关语本意是在 "他的Clojure" 中。 - Peter
12
对于不了解情况的人来说,“富有的那位先生”发明了Clojure。 - erturne
1
@AlexCoventry 您的示例返回 [true true] - user4813927
1
哦,自从我写下那个注释以来,行为一定发生了变化。 - Alex Coventry

8

我偶然发现了这篇关于doall不是递归的博客文章(链接)。对此,我在文章的第一条评论中找到了解决方法,大致如下:

(use 'clojure.walk)
(postwalk identity nested-lazy-thing)

我在一个单元测试中发现这很有用,其中我想强制评估一些 map 的嵌套应用程序以强制出现错误条件。


5
(.getClass (into '() (take 3 (repeatedly rand))))

3
这是一个糟糕的想法。它将输入序列颠倒过来。 - amalloy
3
当然,在这种情况下,反转输入不会有任何影响,因为它们只是三个随机数.... :-) - mikera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接