请在2024 Clojure状况调查!中分享您的想法。

欢迎!有关如何使用此功能,请查看关于页面以获取更多信息。

0
语法和读取器
如果元素使用相同的形式生成,则无法使用字面量语法创建具有唯一成员/键的集合/字典。此类合法形式的示例:(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. 对于列表/向量/集合/字典,语法引号建立了一个对应数据结构的模板。在模板内部,未命名的形式作为递归语法引号处理,但可以通过使用未命名或未命名拼接来免除这种递归引号,在这种情况下,它们将被当作表达式处理,并在模板中用它们的值或值的序列替换。 (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))))
用户=> (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}


在我看来,在设置/映射的读取宏评估它们的参数之前,似乎在进行不必要的重复检查。这个检查应该是可以移除的。即使这个检查没有捕捉到一些错误的重复情况(实际上它做到了),但是因为明显的第二次评估检查会捕捉到所有的真实重复情况,所以它是不必要的。

话虽如此,这个检查是否应该发生本身就不太清楚。如果我尝试创建包含重复成员/键的集合/映射,我将不会得到错误。重复项会无声地被移除或替换。


用户=> (set (list 1 1))
#{1}
用户=> (hash-map 1 2 1 3)
{1 3}


对于用读取语法构造的常数来说,似乎这样做最为一致。

我可以看出,字面表达式的表示并不是“构造请求”,而更像是一种尝试复制字面数据对象的打印表示。从这个角度来看,禁止“非法”的打印表示似乎是有道理的。不幸的是,由于列表在读取时就会进行评估,所以字面向量、集合和映射中的评估形式的存在(已经打破了这种理论)。也就是说,这种集合的打印表示不是一个可准确读的形式,所以读取时的重复检查仍然不能防止打印/读取表示中的似乎是矛盾的一致性。


用户=> '#{(+ 1 1) 2}
#{(+ 1 1) 2}
user=> #{(+ 1 1) 2}

IllegalArgumentException 重复键:2  clojure.lang.PersistentHashSet.createWithCheck (PersistentHashSet.java:56)


鉴于这个问题无法完全避免,将读取器字面量构造器与他们运行时对应的处理方式处理得最简单、最一致,就像语法引号在没有虚假的重复检查的情况下做的那样。

3 个答案

0

评论者:alexmiller

也参见CLJ-1555

0

评论者:bronsa

可能相关:http://dev.clojure.org/jira/browse/CLJ-1425

0
参考:https://clojure.atlassian.net/browse/CLJ-1538(由 alex+import 报告)
...