【问题标题】:How to pass a list to clojure's `->` macro?如何将列表传递给clojure的`->`宏?
【发布时间】:2016-02-08 08:35:35
【问题描述】:

我正在尝试找到一种通过函数列表将值串接的方法。

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

(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 宏:#'clojure.core/->

那么问题来了:如何将值列表传递给-> 宏(或者说,任何其他宏),就像对函数使用apply 所做的那样?

【问题讨论】:

  • 也许这是重复的,In clojure, how to apply a macro to a list? 回答了你的问题。
  • @sloth,感谢您指出这一点。虽然它提供了一些关于如何解决问题的想法,但我更喜欢 leetwinsky 的回答,因为它更符合我的口味,更符合我的口味。
  • -> 的主要目的是让代码更易于阅读。但是,如果您只是编写一个新宏以便可以使用->(在代码中您永远不会看到,因为它只存在于宏扩展中),我不明白这一点。本着从不使用函数的宏的精神,我建议以下解决方案:(reduce #(%2 %) routes middleware)
  • @galdre 您的评论应该是公认的答案。

标签: list clojure macros apply


【解决方案1】:

这是一个 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 %),那么在您的语言中添加另一个实用词就可以了。

【讨论】:

  • @gladre,函数的组合不行。将其全部提取到列表中的唯一目的是测试应用的中间件集。我错过了什么吗?有办法知道哪些函数是由comp 组成的?
  • 我不确定我是否理解这个问题。通过查看传递给comp 的参数,您可以知道哪些fns 是由comp 组成的。如果您想以编程方式检查 fns,请将中间件定义为列表并使用 reduce(解决方案 1)或 apply comp(解决方案 2)。 comped fns 正常抛出异常,带有堆栈跟踪。如果你想在中间注入一个测试,列表方法无论如何都行不通。如果您查看->comp 的源代码,您会发现它们执行完全相同的操作嵌套,因此对于程序流程它们应该是相同的。
  • 如果您想以编程方式检查middleware 以查看它包含哪些功能,则(reduce middleware ...) 的工作量将少于一次性应用comp
【解决方案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))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-06
    • 2011-07-12
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 2019-11-03
    • 1970-01-01
    相关资源
    最近更新 更多