如何在Clojure中解释地图键?

时间:2016-11-18 07:11:01

标签: clojure read-eval-print-loop

我正在尝试使用随机函数确定的键创建地图文字:

user=> {(str (rand-int 5)) "hello" (str (rand-int 5)) "goodbye"}                                            
IllegalArgumentException Duplicate key: (str (rand-int 5))  clojure.lang.PersistentArrayMap.createWithCheck (PersistentArrayMap.java:71)

user=> {(str (rand-int 5)) "hello" (str (rand-int 6)) "goodbye"}    
{"4" "hello", "2" "goodbye"}

Reader似乎将密钥视为未评估列表。

我在文档中找不到任何有关此内容的详细信息。是否有人可以帮我理解这一点?

4 个答案:

答案 0 :(得分:6)

通过Clojure编译器的源代码,我发现了以下内容:

  1. 包含LispReader的类MapReader,其中包含负责阅读地图文字的嵌套类calling。它的方法invoke{}符号之间读取Clojure表单,并通过RT.map {返回地图( Clojure表单) {3}}方法。

  2. RT.map calls PersistentHashMap.createWithCheck实际检查重复密钥is performed。由于我们正在构建 Clojure表单的地图,即使有两个相同的表单评估为不同的值(例如在您的示例中),也会触发检查。

  3. 所有Clojure表单的评估都在Compiler类中进行,特别是地图表单在其嵌套类MapExpr中进行评估。它的方法eval评估地图的键和值,并使用RT.map再次构建持久地图。因此,检查重复键是否也将根据评估值执行,这就是为什么以下代码也会失败的原因:

  4. (let [x :foo y :foo]
      {x :bar y :baz}) ;; throws duplicated key exception
    

    我不确定为什么作者决定在表格地图和价值图上检查重复的密钥。也许,它是某种"失败的快速策略":这样的实现将在编译阶段早期报告错误(尽管可能存在误报)并且此检查不会被保留为运行时。

答案 1 :(得分:4)

读者生成的所有都未经评估。这是读者背后的主要思想:它将表格作为数据读取,其解释极少。读者将未评估的地图提供给编译器。

读者通过assoc或conj递增地构建地图。但是,在过去,这种方法会为您的代码产生更奇怪的结果:{(str (rand-int 5)) "goodbye"}。也就是说,将应用正常的关联规则:添加最后一个键值对获胜。人们遇到了这个问题,所以现在读者在逐步增加值之前执行contains?检查。

本文将更详细地讨论Lisp风格的读者:http://calculist.org/blog/2012/04/17/homoiconicity-isnt-the-point/

答案 2 :(得分:2)

你是对的,因为读者没有评估地图。

请记住,评估是在阅读后发生的。

来自Clojure reader reference documentation

  

尽管如此,大多数Clojure程序都以文本文件的形式开始,读者的任务是解析文本并生成编译器将看到的数据结构。这不仅仅是编译器的一个阶段。读者和Clojure数据表示在许多可能使用XML或JSON等的相同上下文中都有自己的实用程序。

     

有人可能会说读者具有根据字符定义的语法,而Clojure语言具有根据符号,列表,向量,映射等定义的语法。阅读器由函数read表示,它读取下一个表单(来自流的非字符),并返回该表单所代表的对象。

评估者需要未评估的地图来遍历它并评估其键和值。如果该地图具有多次作为关键字的相同形式,则它是无效的地图文字。

您可以使用hash-map功能

(hash-map (rand-int 5) "hello"
          (rand-int 5) "goodbye")`.

请注意,生成的地图可能有一个或两个键,具体取决于键是否不同。

如果你想强制执行两个键呢?像

(zipmap (distinct (repeatedly #(rand-int 5)))
        ["hello" "goodbye"])

答案 3 :(得分:1)

我不知道有关读者问题的答案,但构建此哈希映射的更安全的方法是确保密钥不同。例如:

(let [[k1 k2] (shuffle (range 5))] 
  {k1 "hello" k2 "goodbye"})