14

我以为我理解解构,但我正在阅读 clojure 博客,这让我很困惑。如果你有一个这样写的函数:

(defn f [& {:keys [foo bar]}] 
  (println foo " " bar))

为什么你可以这样称呼它:

(f :foo 1 :bar 2)

我的第一个想法是我的函数应该这样调用:

(f {:foo 1 :bar 2})
IllegalArgumentException No value supplied for key: {:foo 1, :bar 2}  clojure.lang.PersistentHashMap.createWithCheck (PersistentHashMap.java:89)

但显然这行不通。我认为这与&工作方式有关。但我一直认为它后面的东西是一个向量,因此你必须像向量一样解构它后面的任何东西。

有人可以向我解释这个定义是如何/为什么会这样工作的吗?谢谢

4

2 回答 2

13

& 和解构形式按顺序工作:

  • & 将它之后的任何参数收集到一个集合中
  • 然后,map 解构形式获取集合,根据需要从集合中创建一个映射,并将名称绑定到向量中列出的键。

地图解构形式中的向量只是用于构建解构/绑定的语法,并不意味着输入形式之外的任何内容

在 defn 中没有 & 的情况下,第二种形式将起作用,而第一种则不会。
使用 & 第一种形式将起作用,而第二种则不会。

于 2013-05-17T06:29:17.947 回答
7

destructure您可以通过手动调用来查看幕后发生的事情。让我们从一个更简单的例子开始:

user> (destructure ['{foo :foo} {:foo 42}])
[map__26147 {:foo 42}
 map__26147 (if (clojure.core/seq? map__26147)
              (clojure.lang.PersistentHashMap/create
               (clojure.core/seq map__26147))
              map__26147)
 foo (clojure.core/get map__26147 :foo)]

这对应于(let [{foo :foo} {:foo 42}] ...)(你可以用(macroexpand-1 '(let [{foo :foo} {:foo 42}] ...)). -map (好像 by (apply hash-map the-seq)。否则,该值被假定为关联并直接使用。在此提交中添加了 seq 'pouring' 功能。

让我们测试一下:

user> (let [{foo :foo} {:foo 42}] foo)
42
user> (let [{foo :foo} (list :foo 42)] foo)
42
user> (let [{foo :foo} (apply hash-map (list :foo 42))] foo)
42

第一种情况,value不是seq,所以直接使用。在第二种情况下,列表是一个 seq,因此在绑定到{foo :foo}. 第三种情况表明,这种倾倒在语义上等价于(apply hash-map the-seq)

现在让我们看一下您的示例:

user> (destructure '[[& {:keys [foo bar]}] args])
[vec__26204 args
 map__26205 (clojure.core/nthnext vec__26204 0)
 map__26205 (if (clojure.core/seq? map__26205)
              (clojure.lang.PersistentHashMap/create
               (clojure.core/seq map__26205))
              map__26205)
 bar (clojure.core/get map__26205 :bar)
 foo (clojure.core/get map__26205 :foo)]

nthnext位来自&- 在这种情况下,因为在 之前没有固定参数,所以&我们有一个(nthnext vec# 0),这相当于只是转换args为一个 seq (如果需要)。然后我们像上面那样解构地图。因为&我们有一个 seq 的保证,所以映射解构的 seq 特例总是会被触发,并且在绑定到映射形式之前,args 总是会被“注入”到哈希映射中。

如果此示例与您的原始 fn 之间的关系不清楚,请考虑:

user> (macroexpand-1 '(fn [& {:keys [foo bar]}]))
(fn* ([& p__26214] (clojure.core/let [{:keys [foo bar]} p__26214])))
于 2013-05-17T06:32:08.333 回答