我正试图找到一种通过函数列表来线程化值的方法。
首先,我有一个通常的基于铃声的代码:
(defn make-handler [routes]
(-> routes
(wrap-json-body)
(wrap-cors)
;; and so on
))
但这不是最佳的,因为我想编写一个测试来检查路由实际上是用wrap-cors包装的。我决定将包装器解压缩成def。所以代码变成如下:
(def middleware
(list ('wrap-json-body)
('wrap-cors)
;; and so on
))
(defn make-handler [routes]
(-> routes middleware))
这显然不起作用,因为->
宏没有将列表作为第二个参数,所以不应该这样做。所以我尝试使用apply
函数来解决这个问题:
(defn make-handler [routes]
(apply -> routes middleware))
最终拯救了:
CompilerException java.lang.RuntimeException:不能取值a 宏:#'clojure.core / - >
所以问题就出现了:如何将一个值列表传递给->
宏(或者说,任何其他宏),如同一个函数apply
一样?
答案 0 :(得分:6)
这是一个XY问题。
->
的要点是让代码更容易阅读。但是如果只为了使用->
而编写一个新的宏(在代码中没有人会看到因为它仅存在于宏扩展中),在我看来,这是做了很多工作而没有任何好处。此外,我认为它模糊了代码,而不是澄清代码。
所以,本着从不使用函数的宏的精神,我建议以下两个等价的解决方案:
解决方案1
(reduce #(%2 %) routes middleware)
解决方案2
((apply comp middleware) routes)
更好的方式
通过将middleware
的定义从函数列表更改为函数的composition,可以轻松简化第二种解决方案:
(def middleware
(comp wrap-json-body
wrap-cors
;; and so on
))
(middleware routes)
当我开始学习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))
这完全没必要,有些人可能更喜欢直接使用reduce
更清楚。但是,如果您不想在应用程序代码中盯着#(%2 %)
,则可以在您的语言中添加另一个实用程序字。
答案 1 :(得分:2)
你可以为它制作一个宏:
;; notice that it is better to use a back quote, to qoute function names for macro, as it fully qualifies them.
(def middleware
`((wrap-json-body)
(wrap-cors))
;; and so on
)
(defmacro with-middleware [routes]
`(-> ~routes ~@middleware))
例如:
(with-middleware [1 2 3])
会扩展到:
(-> [1 2 3] (wrap-json-body) (wrap-cors))