解构映射参数:key vs old fashioned let

时间:2016-06-17 18:11:26

标签: clojure

从Scheme背景学习Clojure,我的直接本能是编写如下代码:

(defn my-func [data]
  (let [right (:right data)
        left  (:left data)]
     ... body ... ))

然而,我很快就被告知模式匹配是Clojure(或者确实是Haskell和Scala)的一大特色,所以我觉得有必要写:

(defn my-func [ {:keys [right left] } ]
   ... body ...)

所以我的代码现在充斥着这种语法。不知怎的,我不喜欢它。我觉得我的代码看起来不那么吸引人,甚至可能性较差(但这可能是由于缺乏惯用的Clojure经验,所以随着时间的推移它会变得更好)。

所以我想问,是否有一种基本理由使用一种风格而不是另一种风格?例如,编译器是否生成字节码,在第二种情况下效率更高。或者我还应该注意其他原因吗?

3 个答案:

答案 0 :(得分:4)

Destructuring is not pattern matching!当你这样写:

(defn my-func [{:keys [right left]}]
  ,,,)

与此相同:

(defn my-func [data]
  (let [{:keys [right left]} data]
    ,,,))

与此相同:

(defn my-func [data]
  (let [right (:right data)
        left (:left data)]
    ,,,))

As Piotrek pointed out,使用更简洁的表单没有性能优势。但是,仍然有一个好处:即简洁。

As Alan pointed out:keys解构的主要优点是你只需要写一次每个键名而不是两次。

As you pointed out,直接在函数的参数向量中进行解构会使代码的可读性降低,这就是为什么它在Clojure中实际上并不常见。

最棒的是,由于在Clojure中没有使用参数命名进行解构,你可以充分利用两个世界(我在上面给出的第二种形式):

(defn my-func [data]
  (let [{:keys [right left]} data]
    ,,,))

通过这种方式,您可以获得可读性(特别是如果您将参数命名为比data更好的内容)和简洁性(通过使用:keys解构)。

答案 1 :(得分:3)

:keys解构只是一种语法糖,实际上扩展为对get的一系列调用和符号赋值。例如:

(macroexpand-1
  '(let [{:keys [a b]} {:a 1 :b 2}]))

扩展为:

(let*
 [map__2507
  {:a 1, :b 2}
  map__2507
  (if (clojure.core/seq? map__2507) (clojure.lang.PersistentHashMap/create (clojure.core/seq map__2507)) map__2507)
  a
  (clojure.core/get map__2507 :a)
  b
  (clojure.core/get map__2507 :b)])

解构的主要目标(注意它只是解构 - 而不是完全模式匹配)是简洁的代码,并且没有涉及性能优化。

为了说明没有:keys解构的代码如何更详细,请看一下示例:

(let [{:keys [id name address age salary]} person]
  ...)

VS

(let [id (:id person)
      name (:name person)
      address (:address person)
      age (:age person)
      salary (:salary person)]
  ...)

答案 2 :(得分:1)

为了完整起见,这是来自Clojure Library Coding Standards

  

惯用语代码经常使用解构。但是,如果要将子结构作为调用者合同的一部分进行通信,则只应在arg列表中进行解构。否则,在第一行中进行结构化。示例:本书中的my snake code未通过此测试,在arg列表中进行了太多的解构。