为什么可以将键值对传递给破坏地图的函数?

Dan*_*lan 12 clojure destructuring

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

(defn f [& {:keys [foo bar]}] 
  (println foo " " bar))
Run Code Online (Sandbox Code Playgroud)

你为什么这样称呼它:

(f :foo 1 :bar 2)
Run Code Online (Sandbox Code Playgroud)

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

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

但显然这不起作用.我认为这与&工作方式有关.但是我一直认为它之后的东西是一个向量,因此你必须在它之后像向量一样去构造任何东西.

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

Art*_*ldt 13

&和解构表单按顺序工作:

  • &将其后面的任何参数收集到一个集合中
  • 然后,地图解构表单获取集合,需要时从中生成一个映射,并将名称绑定到向量中列出的键.

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

如果没有&在defn中第二种形式将起作用,而第一种形式则不起作用.
随着&第一种形式将起作用,第二种形式将不起作用.


Tom*_*Tom 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)]
Run Code Online (Sandbox Code Playgroud)

这对应于(let [{foo :foo} {:foo 42}] ...)(你可以验证(macroexpand-1 '(let [{foo :foo} {:foo 42}] ...)).输出的第二行是重要的位.地图绑定表单可以以两种方式工作:如果绑定的值是seq,seq将被"倾注"到一个哈希中-map(好像是(apply hash-map the-seq).否则,该值被假定为关联并直接使用.在此提交中添加了seq'pward'功能.

我们来测试一下:

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
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,该值不是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)]
Run Code Online (Sandbox Code Playgroud)

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])))
Run Code Online (Sandbox Code Playgroud)