如何在 Clojure 中解释映射键?

How are map keys interpreted in Clojure?

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

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 似乎将密钥视为未评估的列表。

我在文档中找不到关于此的任何详细信息。有没有人可以帮助我更多地理解这一点?

我不知道有关 reader 的问题的答案,但构建此散列映射的更安全的方法是确保键不同。例如:

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

reader 生成的所有内容 均未计算。这是 reader 背后的主要思想:它将表单作为数据读取,几乎没有解释。 reader 将未计算的映射提供给编译器。

reader 的工作原理是通过 assoc 或 conj 逐步构建地图。但是,在过去,这种方法会为您的代码产生一个偶数 运行ger 的结果:{(str (rand-int 5)) "goodbye"}。也就是说,将应用正常的关联规则:最后添加的键值对获胜。伙计们 运行 解决了这个问题,所以现在 reader 在增量添加值之前执行 contains? 检查。

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

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

  1. 有classLispReader which contains nested class MapReader that is responsible for reading map literals. It's method invoke reads Clojure forms between {, } symbols and returns a map (of Clojure forms) by calling RT.map方法。

  2. RT.mapcalls PersistentHashMap.createWithCheck where actual check for duplicated keys is performed。由于我们正在构建 Clojure 表单 的映射,即使有两个相同的表单计算出不同的值(如您的示例),检查也会触发。

  3. 所有 Clojure 表单的评估在 Compiler class, in particular map forms are evaluated within it's nested class MapExpr. It's method eval 中进行,评估映射的键和值并再次使用 RT.map 构建持久映射。因此,还将针对评估值执行重复键检查,这就是以下代码也会失败的原因:

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

我不确定为什么作者决定对表单映射和值映射执行重复键检查。可能是某种 "fail fast strategy":这样的实现会在编译阶段及早报告错误(即使可能存在误报),并且不会将此检查推迟到运行时。

你说得对,reader 没有评估地图。

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

来自Clojure reader reference documentation:

That said, most Clojure programs begin life as text files, and it is the task of the reader to parse the text and produce the data structure the compiler will see. This is not merely a phase of the compiler. The reader, and the Clojure data representations, have utility on their own in many of the same contexts one might use XML or JSON etc.

One might say the reader has syntax defined in terms of characters, and the Clojure language has syntax defined in terms of symbols, lists, vectors, maps etc. The reader is represented by the function read, which reads the next form (not character) from a stream, and returns the object represented by that form.

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

您可以改用 hash-map 函数

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

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

如果你想强制执行两个密钥,请执行某项操作。喜欢

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