2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面了解此功能的更多信息。

+1
Sequences
编辑

对于集合和序列,当调用它们上的 str 时,Clojure 将返回类似于 pr-str 结果的字符串形式的它们 EDN-like 表示。

(str (vector 1 2 3))
"[1 2 3]"

(str '(1 2 3))
"(1 2 3)"

(str (seq [1 2 3]))
"(1 2 3)"

(str {:a 1 :b 2})
"{:a 1, :b 2}"

(str #{1 2})
"#{1 2}"

但是当用在 lazy-seq 上时,str 将打印 lazy-seq 的类型,后面跟着其实际值的哈希。

(str (lazy-seq [1 2 3]))
"clojure.lang.LazySeq@7861"

这不仅可能被认为是一种相当无用的行为(因为大多数人可能更希望它像 seq 一样进行字符串化。这也会强制 lazy-seq 实现自己。

(let [ls (map inc [1 2 3])]
  (realized? ls))
;;=> false
(let [ls (map inc [1 2 3])]
  (str ls)
  (realized? ls))
;;=> true

同样,当用于 eduction 时,str 将打印出此时为 eduction 的类型,后面跟随内存位置。

(str (eduction identity [1 2 3]))
"clojure.core.Eduction@2a85e4f4"

lazy-seq 不同,它不会“实现”eduction。

(str (eduction (fn[e] (println e) (identity e)) [1 2 3]))
"clojure.core.Eduction@26ae9861"

(str (map (fn[e] (println e) (identity e)) [1 2 3]))
1
2
3
"clojure.lang.LazySeq@7861"

当前对 lazy-seqeduction 的行为似乎要么不一致,要么错误。

理想的做法可能是其中一种

  1. str 将不会导致 lazy-seqeduction 实现自己,但仍然只将它们的类型字符串化。这里的想法是,str 不实现“待定”计算,因此可以安全地用于无限的 lazy-seqs 或重复的 eductions
  2. str 将始终实现“待定计算”,因此 lazy-seqeduction 将与其他集合和序列一样进行字符串化,更类似于在它们上调用 pr-str

附言:在做出决定时考虑 ClojureScript 当前在 lazy-seq 上行为不同,它选择选项 2,而我不确定它对 eduction 做什么可能是有意义的。

cljs.user=> (str (lazy-seq [1 2 3]))
"(1 2 3)"

cljs.user=> (str (eduction identity [1 2 3]))
"[object Object]"

附言2:似乎当 lazy-seqeduction 被嵌套在其他集合或序列中时,它们在这些情况下的行为也不同,即在这些情况下,它们将以 EDN-like 的方式字符串化

(str [1 (map identity [2 3]) 4])
"[1 (2 3) 4]"

(str (seq [1 (map identity [2 3]) 4]))
"(1 (2 3) 4)"

(str [1 (eduction identity [2 3]) 4])
"[1 (2 3) 4]"

(str (seq [1 (eduction identity [2 3]) 4]))
"(1 (2 3) 4)"

这在 ClojureScript 中似乎也是这种情况。

3 个回答

0

您在句子末尾提到“在这种情况下执行 lazy-seq 的选项 1”。您是否指的是选项 2,这从 ClojureScript REPL 交互的示例中似乎是这样?

是的,这是个很好的发现,我已经将其更正为选项 2。
0

以下是一些关于当前实现细节的注释(即不影响请求更改行为实质的注释):您显示的输出示例中的所有十六进制数都是 Java hashCode 方法的返回值,以十六进制表示,这在一般情况下可能不同于 clojure.core/hash 的返回值,或者是相同的,这取决于对象的类以及它是否有定义与 hashCode 不同的 hasheq

对于您给出的 lazy seq 示例,值是不可变的,因此 hashCode 方法的返回值是不可变集合内容的纯函数。

对于所打印的 eduction 值,它仍然是 hashCode 的返回值,但是对于这个类,hashCode 的返回值是定义为 java.lang.Object 类的默认 Java identityHashCode 值,并且没有被重写:[点击查看](https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#identityHashCode(java.lang.Object))

明白了,知道这个很好,所以从某种意义上说,它们都将字符串转换为类型 + hashCode,但它们的 hashCode 实现不同。也许这就是为 lazy-seq 进行强制实现的实现方式?
是的,对懒序列中的 `hashCode` 方法调用会强制整个序列实现,并对每个元素调用 `hashCode`,类似于 `clojure.core/hash` 的方式。它们只是对大多数 Clojure 集合计算不同的值,因为从 Clojure 1.6.0 版本开始,Clojure 针对几种类型的 Clojure 集合改进了 `clojure.core/hash` 返回值的多样性,与一些集合的 `hashCode` 相比,值的多样性相当低(例如 2 元素或 3 元素的向量/序列,所有整数,使用 `hashCode` 的多样性很低)。
一个小贴士:`realized?` 报告的是你可能认为的懒序列的头部元素,而不是整个序列。这是因为当然没有必要使懒序列的尾部也变得懒。

    (let [a (map inc (range))]
      [(realized? a)
       (first a)
       (realized? a)
       (realized? (rest a))])
0
...