Clojure 为什么可以将键值对传递给分解映射的函数?
我以为我理解解构,但我在读clojure的博客,这让我很困惑。如果您编写了一个函数,如下所示:Clojure 为什么可以将键值对传递给分解映射的函数?,clojure,destructuring,Clojure,Destructuring,我以为我理解解构,但我在读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.Persis
(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)
我的第一个想法是我的函数应该这样调用:
(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)
但很明显,这是行不通的。我认为这与&
的工作方式有关。但我一直认为它后面的东西是一个向量,因此你必须像向量一样分解它后面的任何东西
有人能向我解释一下这个定义是如何运作的吗?谢谢按顺序执行&和分解表单:
- &将其后面的所有参数收集到集合中
- 然后,map destructuring表单获取集合,根据需要从中生成一个映射,并将名称绑定到向量中列出的键
map destructuring表单中的向量只是用于构建destructuring/binding的语法,除了输入表单之外,没有任何含义 如果没有&在defn中,第二种形式有效,而第一种形式无效。
使用&第一个表单有效,第二个表单无效。您可以通过手动调用
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)]
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)]
这对应于(let[{foo:foo}{:foo 42}]…)
(正如您可以通过(macroexpand-1'(let[{foo:foo}{:foo 42}]…)
验证的那样)。输出的第二行是重要的位。映射绑定形式可以通过两种方式工作:如果绑定的值是seq,则seq将“倒”到哈希映射中(就像通过(将哈希映射应用于seq)
。否则,将假定该值为关联值并直接使用。添加了seq“倾倒”功能
让我们测试一下:
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
在第一种情况下,值不是seq,因此直接使用它。在第二种情况下,列表是seq,因此在绑定到{foo:foo}
之前将其“倒”到哈希映射中。第三种情况表明,这种倒在语义上等同于(应用哈希映射seq)
现在让我们看一下类似于您的示例的内容:
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)]
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特例将始终被触发,并且在绑定到映射表单之前,参数将始终“倒”到哈希映射中
如果此示例与原始fn之间的关系不清楚,请考虑:
user> (macroexpand-1 '(fn [& {:keys [foo bar]}]))
(fn* ([& p__26214] (clojure.core/let [{:keys [foo bar]} p__26214])))
“地图解构形式中的向量只是语法”——事实上,列表在这里也同样适用。