请在2024 年 Clojure 调查问卷!中分享您的想法。

欢迎!请参阅关于页面获取更多关于此功能的详细信息。

+1
序列
编辑

对于集合和序列,当在对它们调用 str 时,Clojure 会返回它们作为字符串的 EDN-like 表示形式,这更接近于 pr-str 的结果。

(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

同样,当用于 eductions 时,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-seq 或重复 eduction 时使用是安全的。
  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 类 java.lang.Object 中定义的默认 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集合计算不同的值,因为与`hashCode`相比,Clojure 1.6.0提高了Clojure几种类型的集合返回`clojure.core/hash`的值的变异性,对于某些类型的集合(例如,2个元素或3个元素的向量/序列,所有整数,`hashCode`的变异性相当低)。
一个小贴士:`realized?`报告的是你可能认为的懒序列的头元素,而不是整个序列。这是因为在懒序列中,尾部也有可能是懒的,当然没有任何理由需要这样的懒惰。

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