如何将列表传递给 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 %),可以在您的语言中添加另一个实用词。