Clojure“;concat“;不偷懒

Clojure“;concat“;不偷懒,clojure,lazy-evaluation,Clojure,Lazy Evaluation,我一直在测试concat行为 文档字符串显示: 返回一个惰性seq,表示中元素的串联 供应的colls 然而,concat的参数似乎并不懒惰。相反,我们观察通常的热切评价。这不是我所期望的 注意: 下面是从“Clojure的欢乐,第二版”第页生成包含整数的二叉树的简单代码。208: 使xseq真正懒惰需要在concat前面增加一个lazy seq: (defn xseq-p2 [t k] (if (nil? t) (println k "▼" "⊥") (println k "▼" (ge

我一直在测试
concat
行为

文档字符串显示:

返回一个惰性seq,表示中元素的串联 供应的colls

然而,
concat
的参数似乎并不懒惰。相反,我们观察通常的热切评价。这不是我所期望的

注意:

下面是从“Clojure的欢乐,第二版”第页生成包含整数的二叉树的简单代码。208:

使
xseq
真正懒惰需要在
concat
前面增加一个
lazy seq

(defn xseq-p2 [t k]
   (if (nil? t) (println k "▼" "⊥") (println k "▼" (get t :val)))
   (when (some? t)
      (lazy-seq
      (concat (xseq-p2 (get t :left) (str k "[" (get t :val) "]" "◀"))
              [ (get t :val) ]
              (xseq-p2 (get t :right) (str k "[" (get t :val) "]" "▶"))))))
现在它是懒惰的:

(type (xseq-p2 ll ""))
; ▼ 3
; clojure.lang.LazySeq

(take 2 (xseq-p2 ll ""))
; ▼ 3
; ([3]◀ ▼ 2
; [3]▶ ▼ 5
; [3]◀[2]◀ ▼ ⊥
; [3]◀[2]▶ ▼ ⊥
; 2 3)
这是预期的吗

p.S.

另一种方法是延迟两个下降方向(或仅延迟向右的下降方向)。当两个下降都延迟时,
xseq-p3
甚至比
xseq-p1
更延迟:

(defn xseq-p3 [t k]
   (if (nil? t) (println k "▼" "⊥") (println k "▼" (get t :val)))
   (when (some? t)
      (let [ left   (get t :left)
             v      (get t :val)
             right  (get t :right)
             l-seq  (lazy-seq (xseq-p3 left  (str k "[" v "]" "◀")))
             r-seq  (lazy-seq (xseq-p3 right (str k "[" v "]" "▶"))) ]
         (concat l-seq [v] r-seq))))

(type (xseq-p3 ll ""))
; ▼ 3
; clojure.lang.LazySeq

(take 2 (xseq-p3 ll ""))
; ▼ 3
; ([3]◀ ▼ 2
; [3]◀[2]◀ ▼ ⊥
; [3]◀[2]▶ ▼ ⊥
; 2 3)

任何作为参数传递给Clojure函数的表达式都会被急切地求值,因此函数代码只能看到一个值。它可以是基本值(例如
42
)或内置值(例如
“hello”
)或复合值(例如
[42“hello”{:a1:b2}]
)。该值可能是由
(range)
生成的延迟序列

请注意,如果键入
(取3(范围))
,则
功能不会看到
(范围)
部分。它看起来像
(取3)
。因此表达式
(range)
中的函数调用被急切地求值,它产生的惰性序列被传递给
take
表达式

如果arg是惰性序列,则函数本身不知道这一点。您可以使用
println
等插入生成惰性seq以观察计时,但这不会影响函数如何通过
(第一个参数)
(第三个参数)
等使用值。通常,您只关心函数如何生成惰性结果,也许还关心它消耗输入序列中的多少元素(懒惰与否)

您还应该知道,Clojure中的大多数惰性序列都是以32个块的长度运行的,以提高效率。这意味着惰性序列实际上可以做的工作比预期的要多。例如,假设您只想消耗3个“昂贵的”由于在请求第一个项目时,分块通常会生成32个项目,因此您已经完成了不必要和不必要的额外工作


我通常避免使用惰性序列,因为它们在什么时候运行以及序列中有多少项将被实现是不可预测的。因此,我总是使用
mapv
filterv
&friends,并用
(vec…
大量包装其他东西(例如,我有自己的非惰性
for v
)。我只在输入/输出真正“大”时才使用惰性序列(例如,处理大型数据库表中的每一行)。

在这里可能会有所帮助。@glts啊,是的,在
xseq-p1
中使用
lazy cat
而不是
cat
使其表现得像
xseq-p3
!那么什么是“惰性”呢关于
concat
?我想说,只有当它的参数一开始是懒惰的时候,它才会表现懒惰。关于分块的警告很好,但我不会说“大多数”惰性序列是分块的。有些是分块的,因此如果你关心分块,那么你需要小心处理所有序列,但是有很多方法可以生成不分块的惰性序列。谢谢。你说“所有clojure函数都急切地评估它们的参数(即,在参数传递给函数代码之前)。”但这不是真的吗?!在
xseq-p3
中,
lazy seq
后面的递归调用显然是“挂起”:虽然返回了底层类型的东西
clojure.lang.LazySeq
,但树的顺序遍历还没有真正开始,遍历只触及根节点,打印
▼ 3
。返回的“thunk”中的任何内容只有在实际需要第一个元素时才会被“stepped”。
(type (xseq-p2 ll ""))
; ▼ 3
; clojure.lang.LazySeq

(take 2 (xseq-p2 ll ""))
; ▼ 3
; ([3]◀ ▼ 2
; [3]▶ ▼ 5
; [3]◀[2]◀ ▼ ⊥
; [3]◀[2]▶ ▼ ⊥
; 2 3)
(defn xseq-p3 [t k]
   (if (nil? t) (println k "▼" "⊥") (println k "▼" (get t :val)))
   (when (some? t)
      (let [ left   (get t :left)
             v      (get t :val)
             right  (get t :right)
             l-seq  (lazy-seq (xseq-p3 left  (str k "[" v "]" "◀")))
             r-seq  (lazy-seq (xseq-p3 right (str k "[" v "]" "▶"))) ]
         (concat l-seq [v] r-seq))))

(type (xseq-p3 ll ""))
; ▼ 3
; clojure.lang.LazySeq

(take 2 (xseq-p3 ll ""))
; ▼ 3
; ([3]◀ ▼ 2
; [3]◀[2]◀ ▼ ⊥
; [3]◀[2]▶ ▼ ⊥
; 2 3)