如果元素以相同的形式生成,则我不能使用字面量语法创建具有唯一成员/键的集合/映射。此类合法形式的示例:(rand)、(read)、(clojure.core.async/<!!)、等。我将使用(rand)在这些示例中。
user=> #{(rand) (rand)}
IllegalArgumentException 重复键:(rand) clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:68)
user=> {(rand) 1, (rand) 2}
IllegalArgumentException 重复键:(rand) clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
似乎在评估集合构造函数的参数之前就检查了重复项。然而,这并不能阻止以后再次进行检查。
注意,即使在重复项作为源中的字面量出现之前,复制也仍(正确)被检测到
user=> #{(+ 1 1) 2}
IllegalArgumentException 重复键:2 clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:56)
user=> {(+ 1 1) :a, 2 :b}
IllegalArgumentException 重复键:2 clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
因此,第一次重复项检查似乎既多余又错误。
请注意,这种急切的重复项检查似乎比语法引号读取宏的优先级更高。
user=> `#{~(rand) ~(rand)}
IllegalArgumentException 重复键:(clojure.core/unquote (rand)) clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:68)
user=> `{~(rand) 1, ~(rand) 2}
IllegalArgumentException 重复键:(clojure.core/unquote (rand)) clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:70)
这很奇怪 —— 因为语法引号在读取时不应该实现任何集合。
bq. 对于列表/向量/集合/映射,语法引号为相应的数据结构建立一个模板。在模板内,未限定的形式表现得像递归语法引号,但形式可以通过使用限定的unquote或unquote-splicing来免除这种递归引号,在这种情况下,它们将被视为表达式并替换为模板中的值或值序列。(
https://clojure.org/reader)
不顾定义,基于语法引号看似的展开,我期待之前的操作能够正确执行。
通过对所需的输入进行手动替换,我得到了预期的结果
user=> '`#{~:a ~:b}
(clojure.core/apply clojure.core/hash-set (clojure.core/seq (clojure.core/concat (clojure.core/list :b) (clojure.core/list :a))))
user=> (clojure.core/apply clojure.core/hash-set (clojure.core/seq (clojure.core/concat (clojure.core/list (rand)) (clojure.core/list (rand)))))
#{0.27341896385866227 0.3051522362827035}
user=> '`{~:a 1, ~:b 2}
(clojure.core/apply clojure.core/hash-map (clojure.core/seq (clojure.core/concat (clojure.core/list :a) (clojure.core/list 1) (clojure.core/list :b) (clojure.core/list 2))))
user=> (clojure.core/apply clojure.core/hash-map (clojure.core/seq (clojure.core/concat (clojure.core/list (rand)) (clojure.core/list 1) (clojure.core/list (rand)) (clojure.core/list 2))))
{0.12476921225204185 2, 0.5807961046096718 1}
在我看来,在集合/映射的阅读宏计算参数之前,似乎正在进行一个多余的重复检查。这项检查似乎是应该被移除的。即使这项检查没有捕捉到一些错误阳性重复(它的确做到了),由于看起来第二次评估后的检查会捕获所有真正重复的内容,它也是不必要的。
尽管如此,不清楚这项检查是否应该发生。如果我尝试创建包含重复成员/键的集合/映射,我不会收到错误。重复内容会被静默删除或替代。
user=> (set (list 1 1))
#{1}
user=> (hash-map 1 2 1 3)
{1 3}
似乎对于由读取语法构造的文本文诸如同样一致。
我能理解这种字面表示并不是一个‘请求构造’,而是一种尝试模拟字面数据对象的打印表示。从这个角度来看,禁止‘非法’的打印表示似乎是合理的。不幸的是,由于列表在读取时就会评估,所以字面向量、集合和映射内部已经打破了这种理论。也就是说,这种集合的打印表示*不是*一个准确的可读形式,因此读取时的重复检查仍然不能防止打印/读取表示中的看似不一致的问题。
user=> '#{(+ 1 1) 2}
#{(+ 1 1) 2}
user=> #{(+ 1 1) 2}
IllegalArgumentException 重复键:2 clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:56)
由于问题不可能完全避免,因此将读取器字面构造器视为其运行时对应版本似乎是最简单、最一致的。正如语法引号在没有这种无用的重复检查时所做的。