Путаница и макросы пространства имен

Я хочу написать макрос, который использует функции из библиотеки 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))

Если макрос был вызван со следующими аргументами (с интервалом бюджета .time/next-date freq) и интервалом и частотой, где "недели" и "2" соответственно, макрораскрытие будет выглядеть примерно так (clj-time. ядро/недели 2)

Всякий раз, когда я пытаюсь это сделать из REPL, он не может разрешить пространство имен.

Есть ли способ заставить макрос разрешить интервал для аргументов в пространстве имен clj? Каков наилучший способ сделать это?

Спасибо!

Ответ 1

Макросы возвращают список, который затем оценивается в пространстве имен, из которого он вызывается, а не в пространстве имен, в котором он определен. Это отличается от функций, которые оцениваются в пространстве имен, в котором они определены. Это , потому что макросы возвращают выполняемый код, а не просто запускают его.

если я перехожу в другое пространство имен, например hello.core и разворачиваю вызов на следующую дату, я получаю:

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

то после разложения недели разрешается с hello.core, в котором он, конечно, не определен. Чтобы исправить это, нам нужен возвращенный символ для переноса с ним информации о пространстве имен.

К счастью, вы можете явно разрешить символ в пространстве имен с ns-resolve. Требуется пространство имен и символ и пытается найти его в пространстве имен, возвращающем нуль, если он не найден

(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-время повсюду, но это не совсем так.