我以为我理解了解构,但我正在阅读一个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)
但显然这不起作用。我认为这与&
的工作方式有关。但是我一直认为它之后的东西是一个向量,因此你必须在它之后像向量一样去构造任何东西。
有人可以向我解释这个定义如何/为何如此有效?感谢
答案 0 :(得分:13)
&和解构形式顺序工作:
地图解构形式中的向量只是用于构建解构/绑定的语法,并不暗示输入形式的任何内容
没有&在defn中,第二种形式将起作用,而第一种形式则不起作用 与&第一种形式将起作用,第二种形式不起作用。
答案 1 :(得分: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}] ...))
验证的那样。输出的第二行是重要的位。地图绑定表单可以两种方式工作:如果被绑定的值是一个seq,seq将被“倾倒”到一个哈希映射中(好像是(apply hash-map the-seq)
。否则,该值被假定为一个关联并直接使用.seq'pouring'功能被添加到{{ 3}}
让我们测试一下:
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}
之前将其“倾倒”到哈希映射中。第三种情况表明,这种倾注在语义上等同于(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])))