如何将列表传递给 clojure 的 `->` 宏?
How to pass a list to clojure's `->` macro?
我正在尝试找到一种通过函数列表将值串联起来的方法。
首先,我有一个普通的基于环的代码:
(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: Can't take value of a
macro: #'clojure.core/->
所以问题出现了:如何将值列表传递给 ->
宏(或者说,任何其他宏),就像将 apply
传递给函数一样?
你可以为此制作一个宏:
;; 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))
这是一个 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
定义在 core:
(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 %)
,可以在您的语言中添加另一个实用词。
我正在尝试找到一种通过函数列表将值串联起来的方法。
首先,我有一个普通的基于环的代码:
(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: Can't take value of a macro: #'clojure.core/->
所以问题出现了:如何将值列表传递给 ->
宏(或者说,任何其他宏),就像将 apply
传递给函数一样?
你可以为此制作一个宏:
;; 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))
这是一个 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
定义在 core:
(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 %)
,可以在您的语言中添加另一个实用词。