epl*_*oko 1 macros list clojure apply
我正在尝试找到一种通过函数列表来线程化值的方法.
首先,我有一个通常的基于铃声的代码:
(defn make-handler [routes]
(-> routes
(wrap-json-body)
(wrap-cors)
;; and so on
))
Run Code Online (Sandbox Code Playgroud)
但这不是最优的,因为我想编写一个测试来检查路由实际上是用wrap-cors包裹的.我决定将包装器解压缩成def.所以代码变成如下:
(def middleware
(list ('wrap-json-body)
('wrap-cors)
;; and so on
))
(defn make-handler [routes]
(-> routes middleware))
Run Code Online (Sandbox Code Playgroud)
这显然不起作用,并不应该因为->宏没有将列表作为第二个参数.所以我尝试使用该apply函数来解决这个问题:
(defn make-handler [routes]
(apply -> routes middleware))
Run Code Online (Sandbox Code Playgroud)
最终拯救了:
CompilerException java.lang.RuntimeException:无法获取宏的值:#'clojure.core/ - >
所以问题就出现了:如何将一个值列表传递给->宏(或者说,任何其他宏),就像apply对函数一样?
这是一个XY问题.
重点->是使代码更容易阅读.但是如果一个人为了使用而编写一个新宏->(在代码中没有人会看到因为它只存在于宏扩展中),在我看来,这样做了很多工作而没有任何好处.而且,我认为它模糊了代码,而不是澄清代码.
因此,本着从不使用函数将执行的宏的精神,我建议以下两个等效的解决方案:
解决方案1
(reduce #(%2 %) routes middleware)
Run Code Online (Sandbox Code Playgroud)
解决方案2
((apply comp middleware) routes)
Run Code Online (Sandbox Code Playgroud)
更好的方式
通过将middleware函数列表的定义更改为函数的组合,可以轻松简化第二种解决方案:
(def middleware
(comp wrap-json-body
wrap-cors
;; and so on
))
(middleware routes)
Run Code Online (Sandbox Code Playgroud)
当我开始学习Clojure时,我经常遇到这种模式,以至于我的许多早期项目都有一个freduce核心定义:
(defn freduce
"Given an initial input and a collection of functions (f1,..,fn),
This is logically equivalent to ((comp fn ... f1) input)."
[in fs]
(reduce #(%2 %) in fs))
Run Code Online (Sandbox Code Playgroud)
这完全没必要,有些人可能更喜欢直接使用reduce更清晰.但是,如果您不喜欢#(%2 %)在应用程序代码中盯着,可以在您的语言中添加另一个实用程序单词.