使用线程宏时,命名函数和匿名函数之间的奇怪差异

7

我对Clojure中的线程宏可能有所遗漏。

我有一个值为映射的地图,我想在另一个查找结果中查找。让地图简单些:{:a {:b 2}} -- 首先,我想查找键:a,这将产生{:b 2},然后查找b,结果是2。第二个查找的关键字需要是函数的结果。

((fn [x] (get x :b)) ({:a {:b 2} } :a ))
=> 2

好的,让我们使用线程宏使其更易读。

(-> {:a {:b 2} } :a (fn [x] (get x :b)))

即,在地图上将:a作为函数应用,然后应用另一个函数。这样做是不起作用的:CompilerException java.lang.IllegalArgumentException: Parameter declaration :a should be a vector

奇怪的是,如果将匿名函数提取到命名函数中,则可以正常工作:

(defn f [x] (get x :b))
(-> {:a {:b 2} } :a f)
=> 2

甚至可以这样说:
(def f (fn [x] (get x :b)) ) 
(-> {:a {:b 2} } :a f)
=> 2

为什么命名函数和匿名函数的工作方式存在差异?
2个回答

4
线程宏(threading macro)会看到并改变序列中每个子表单,在评估该形式之前,通过将先前的形式递归地插入为每个子表单的第一个参数进行更改。
您可以从以下内容开始:
(-> {:a {:b 2} } :a (fn [x] (get x :b)))

这将变成:

(-> (:a {:a {:b 2}}) (fn [x] (get x :b)))

这将变成:

(fn (:a {:b {:b 2}}) [x] (get x :b)))

显然这不是你想要的。

但是如果你在匿名函数周围添加额外的括号,看看会发生什么:

(-> {:a {:b 2}} :a ((fn [x] (get x :b))))

(-> (:a {:a {:b 2}}) ((fn [x] (get x :b))))

(-> ((fn [x] (get x :b)) (:a {:a {:b 2}})))

((fn [x] (get x :b)) (:a {:a {:b 2}}))

在现在的->形式的最后一个递归宏展开中,我们留下了有效的代码,可以实现您想要的功能。

嗯,有趣,谢谢。我需要理解宏——显然,“每个子表单的第一个参数”并不意味着“匿名函数的第一个参数”——这就是我最开始的想法。 - Mate Varga
是的,宏非常字面意义上的,因此它看到并修改的不是 fn,而是对 fn 的调用。 - noisesmith

3
为了补充noisesmith的回答,在这种情况下,您不需要使用线程宏。从嵌套的映射中获取值的惯用方式是get-in。例如:
(get-in {:a {:b 2}} [:a :b])

=>

2

更好的 :) 谢谢。 - Mate Varga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接