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. 对列表/向量/集合/映射,语法引用建立了一个相应数据结构的模板。在模板中,未限定的形式表现得就像递归语法引用,但可以通过使用 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))))
用户=> (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
by

评论由: alexmiller 发布

也参见 CLJ-1555

0
by

评论由: bronsa 发布

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

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