命名空间混淆和宏

use*_*564 10 macros namespaces clojure

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

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

(budget.time/next-date interval frequency)
Run Code Online (Sandbox Code Playgroud)

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

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

(defmacro next-date [interval freq]
  `(~interval ~freq))
Run Code Online (Sandbox Code Playgroud)

如果使用以下参数(budget.time/next-date interval freq)调用宏,并且间隔和freq分别调用"周"和"2",那么宏展开将看起来像这样(clj-time.core/weeks 2)

每当我从REPL尝试这个时,它都无法解析命名空间.

有没有办法强制宏来解析clj-time命名空间的参数的间隔?做这个的最好方式是什么?

谢谢!

Art*_*ldt 13

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

如果我去另一个名称空间,例如hello.core并将一个调用扩展到next-date我得到:

hello.core> (macroexpand-1 '(next-date weeks 2))
(weeks 2) 
Run Code Online (Sandbox Code Playgroud)

然后在扩展之后,几周从hello.core解决,其中当然没有定义.为了解决这个问题,我们需要返回的符号来携带名称空间信息.

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

(ns-resolve 'clj-time.core (symbol "weeks"))
#'clj-time.core/weeks
Run Code Online (Sandbox Code Playgroud)

接下来你的宏将采用一个符号和一个数字,所以我们可以免除显式调用 symbol

(ns-resolve 'clj-time.core 'weeks)
#'clj-time.core/weeks
Run Code Online (Sandbox Code Playgroud)

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

(defmacro next-date [interval freq]
  (list (ns-resolve 'clj-time.core interval) freq))
Run Code Online (Sandbox Code Playgroud)

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

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

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

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