【问题标题】:Namespace confusion and macros命名空间混淆和宏
【发布时间】:2012-03-29 12:32:55
【问题描述】:

我想编写一个使用 clj-time 库中的函数的宏。在一个命名空间中,我想这样调用宏:

(ns budget.account
  (:require [budget.time]))

(budget.time/next-date interval frequency)

下一个日期宏将在另一个文件中定义,如下所示:

(ns budget.time
  (:require [clj-time.core :as date]))

(defmacro next-date [interval freq]
  `(~interval ~freq))

如果使用以下参数 (budget.time/next-date 间隔频率) 和间隔和频率调用宏,其中 "weeks" 和 "2" 分别是 "weeks" 和 "2",那么宏展开将看起来像这样 (clj-time.核心/周 2)

每当我从 REPL 尝试此操作时,它都无法解析命名空间。

有没有办法强制宏将时间间隔解析为 clj-time 命名空间的参数?最好的方法是什么?

谢谢!

【问题讨论】:

  • 为什么要写宏。这不能通过常规功能完成吗?记住:当函数可以运行时,你永远不应该编写宏。

标签: macros clojure namespaces


【解决方案1】:

宏返回一个列表,然后在调用它的命名空间中进行评估,而不是在其中定义它的命名空间。这与在定义它们的命名空间中评估的函数不同。这是因为宏返回要运行的代码,而不是仅仅运行它。

如果我转到另一个命名空间,例如 hello.core 并将调用扩展到下一个日期,我得到:

hello.core> (macroexpand-1 '(next-date weeks 2))
(weeks 2) 

那么在扩展之后,weeks 是从 hello.core 中解析出来的,当然在里面是没有定义的。为了解决这个问题,我们需要返回的符号来携带名称空间信息。

幸运的是,您可以使用ns-resolve 显式解析命名空间中的符号。它需要一个命名空间和一个符号,并尝试在命名空间中找到它,如果找不到则返回 nil

(ns-resolve 'clj-time.core (symbol "weeks"))
#'clj-time.core/weeks

接下来你的宏将使用一个符号和一个数字,这样我们就可以省去对symbol的显式调用

(ns-resolve 'clj-time.core 'weeks)
#'clj-time.core/weeks

所以现在你只需要一个函数来解析该函数​​,然后创建一个已解析函数的列表,后跟数字,

(defmacro next-date [interval freq]
  (list (ns-resolve 'clj-time.core interval) freq))

在上面的宏中,它所做的只是进行一个立即调用的函数调用,所以你甚至不需要宏:

(defn next-date [interval freq]
  ((ns-resolve 'clj-time.core interval) freq))
(next-date 'weeks 2)
#<Weeks P2W>

非宏版本要求您引用间隔,因为它不需要在您查找之前对其进行评估。宏在这里真正为您买的是不必包含报价,代价是要求所有调用者都需要 clj-time

当然,你也可以在任何地方都需要 clj-time,但这并不是重点。

【讨论】:

  • 到处都需要clj-time还是使用ns-resolve更好?
  • 我个人认为在任何地方都需要 clj-time 作为更清洁的选择。
猜你喜欢
  • 2012-09-06
  • 1970-01-01
  • 2014-04-14
  • 2022-06-17
  • 1970-01-01
  • 2023-01-30
  • 2011-12-28
  • 1970-01-01
  • 2023-03-14
相关资源
最近更新 更多