如何将列表传递给clojure的` - >`宏?

时间:2016-02-08 08:35:36

标签: list clojure macros apply

我正试图找到一种通过函数列表来线程化值的方法。

首先,我有一个通常的基于铃声的代码:

(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一样?

2 个答案:

答案 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))