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.lang.Object类的默认Java identityHashCode值,且没有被重写:https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#identityHashCode(java.lang.Object)

我明白了,知道了这些很好,这样一来,它们的字符串化是可以作为类型加hashCode,但它们的hashCode实现是不同的。或许这正是lazy-seq实现迫使它也要强制实现的?
是的,对lazy sequences调用的`hashCode`方法会强制整个序列实现,并对每个元素调用`hashCode`,类似于`clojure.core/hash`所做的。他们只是对于大多数Clojure集合计算不同的值,因为自从Clojure 1.6.0以来,Clojure在`clojure.core/hash`返回的值的多样性上有所改进,相比之前的`hashCode`,后者在某种类型的集合中值的多样性相当低(例如,2个元素的向量/序列、所有整数,使用`hashCode`的多样性相当低)。
一个小贴士:`realized?`报告的是您可能认为的懒惰序列的“头部元素”,而不是整个序列。这是因为自然没有必要让懒惰序列的尾部也是懒惰的。

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