【问题标题】:Idiomatic clojure map lookup by keyword按关键字查找惯用的 clojure 映射
【发布时间】:2011-10-25 11:25:03
【问题描述】:

假设我有一个使用关键字作为键的 clojure 映射:

(def my-car {:color "candy-apple red" :horsepower 450})

我知道我可以通过将关键字或映射用作函数,将另一个用作参数来查找与关键字关联的值:

(my-car :color)
; => "candy-apple red"
(:color my-car)
; => "candy-apple red"

我意识到这两种形式在某些情况下都可以派上用场,但其中一种形式是否被认为更适合如上所示的直接用法?

【问题讨论】:

    标签: clojure


    【解决方案1】:

    (:color my-car) 是相当标准的。这有几个原因,我不会全部讨论。但这里有一个例子。

    因为:color是一个常量,而my-car不是,hotspot可以完全内联color.invoke(m)的动态调度,这是m.invoke(color)做不到的(在一些java伪代码中)。

    如果 my-car 有时恰好是带有 color 字段而不是普通映射的记录,那就更好了:clojure 编译器可以发出代码来检查“嘿,如果 my-car 是 CarType 的实例,然后返回my-car.color;否则执行所有复杂、缓慢的哈希图查找。”

    【讨论】:

    • 对我来说,最有说服力的原因是:(:color nil) 返回 nil,而 (nil :color) 抛出异常。
    • @user100464 但同样的论点也适用于另一种形式!如果您正在动态访问密钥,({:color "red"} nil) 返回 nil(nil {:color "red"}) 会引发异常。
    • 根据我的测试结果,在REPL,(get hash-map key)最快,(hash-map key)次之,(key hash-map)是这三种形式中最慢的。结果相当一致。
    【解决方案2】:

    来自library coding standards

    • 使用关键字优先语法访问对象的属性:

      (:property object-like-map)
      
    • 使用集合优先语法从集合中提取值(如果集合可能为 nil,则使用 get)。

      (collection-like-map key)
      (get collection-like-map key)
      

    【讨论】:

    • 你能解释一下“类对象图”和“类集合图”的区别吗?
    • @Peeja 我不能 100% 确定这一点,但我将“类似映射的对象”解释为具有预定义键及其属性的映射,例如将 {:first first, :last last, :age age } 设为 Person 对象,而将从数据库中读取的人名映射为键,将他们的信息作为值作为“collection like map”的示例。另请注意,从数据库中读取的名称更可能是字符串而不是关键字,因此无论如何都排除使用第一个查找表单。
    【解决方案3】:

    我列出了支持和反对这两种形式的论据。 (编辑:添加了第三个选项 - (get map :key),尽管有点冗长,但这是我的新宠)

    (:key map) 的参数

    1) 编码标准中要求

    http://dev.clojure.org/display/community/Library+Coding+Standards

    2) 当 map 为 nil 时仍然有效

    > (:a nil)
      nil
    > (nil :a)
      ERROR: can't call nil
    

    ---counterargument---如果key可能为nil,其他形式更好

    > ({:a "b"} nil)
      nil
    > (nil {:a "b"})
      ERROR: can't call nil
    

    3) 更适合对象集合的线程化和映射

    (-> my-map
      :alpha
      fn-on-alpha
      :beta
      fn-on-beta
      :gamma
    
    > (def map-collection '({:key "values"} {:key "in"} {:key "collection"}))
    > (map :key map-collection)
      ("values" "in" "collection")
    

    ---counterargument---线程的代码结构不同于 通常,因此可以将不同的惯用倾向应用于地图访问 需要时

    4) 潜在的优化收益? (需要验证)

    (map :key) 的参数

    1) key 为非关键字或 nil 时不报错

    > ({:a "b"} nil)
      nil
    > (nil {:a "b"})
      ERROR: can't call nil
    > ({"a" "b"} "a")
      "b"
    > ("a" {"a" "b"})
      ERROR: string cannot be cast to IFn
    

    2) 与 Clojure 中的列表访问保持一致

    > ([:a :b :c] 1)
      :b
    > (1 [:a :b :c])
      ERROR: long cannot be cast to IFn
    

    3) 与其他形式的对象访问的相似性

    java>         my_obj  .alpha  .beta  .gamma  .delta
    clj >     ((((my-map  :alpha) :beta) :gamma) :delta)
    clj > (get-in my-map [:alpha  :beta  :gamma  :delta])
    cljs> (aget   js-obj  "alpha" "beta" "gamma" "delta")
    

    4) 访问同一地图中的多个键时的对齐方式(单独的行)

    > (my-func
        (my-map :un)
        (my-map :deux)
        (my-map :trois)
        (my-map :quatre)
        (my-map :cinq))
    > (my-func
        (:un my-map)
        (:deux my-map)
        (:trois my-map)
        (:quatre my-map)
        (:cinq my-map))
    

    ---counterargument---从多个map访问同一个key时对齐更差

    > (my-func
        (:key map-un)
        (:key map-deux)
        (:key map-trois)
        (:key map-quatre)
        (:key map-cinq)
    > (my-func
        (map-un :key)
        (map-deux :key)
        (map-trois :key)
        (map-quatre :key)
        (map-cinq :key)
    

    (get map :key) 的参数

    1) 如果 arg1 是 map/vector/nil 并且 arg2 是 key/index/nil,则永远不会导致错误

    > (get nil :a)
      nil
    > (get nil nil)
      nil
    > (get {:a "b"} nil)
      nil
    > (get {:a "b"} :q)
      nil
    > (get [:a :b :c] nil)
      nil
    > (get [:a :b :c] 5)
      nil
    

    2) 与其他 Clojure 函数的形式一致

    > (get {:a "b"} :a)
      :b
    > (contains? {:a "b"} :a)
      true
    > (nth [:a :b :c] 1)
      :b
    > (conj [:a :b] :c)
      [:a :b :c]
    

    3) 地图优先的对齐优势

    > (my-func
        (get my-map :un)
        (get my-map :deux)
        (get my-map :trois)
        (get my-map :quatre)
        (get my-map :cinq))
    

    4) Get-in 可用于一次调用的嵌套访问

    > (get-in my-map [:alpha  :beta  :gamma  :delta])
    > (aget   js-obj  "alpha" "beta" "gamma" "delta")
    

    来源:在http://tryclj.com/上测试

    【讨论】:

    • 仅供参考 - “如果没有找到值,则可以提供默认值”在使用关键字作为函数时也是如此:(:foo {:bar "baz} 4) 返回4
    • @ChrisOakman 你是对的。为所有形式的访问提供默认值,因此我将其从列表中删除。
    【解决方案4】:

    我会说两者都是惯用的。唯一需要注意的是,第二种形式仅适用于关键字。我认为,作为一个深思熟虑的设计选择,它会更有理由成为惯用的。

    【讨论】:

    • 它也适用于符号,以及通过在关联中查找自身来实现 IFn 的任何其他东西。 (let [k 'key-sym, m {k 1}] (k m)) ;=> 1。同样,(m k) 表单不适用于记录,因为它们没有实现 IFn(有充分的理由,即使它不方便)。
    猜你喜欢
    • 2011-11-04
    • 1970-01-01
    • 1970-01-01
    • 2013-03-08
    • 2017-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多