Помогите мне написать макрос Clojure, который автоматически добавляет метаданные в определение функции - PullRequest
19 голосов
/ 13 июня 2009

Я понимаю, что первое правило Макроклуба - не используйте макросы, поэтому следующий вопрос предназначен скорее как упражнение в изучении Clojure, чем что-либо еще (я понимаю, что это не обязательно лучшее использование макросов).

Я хочу написать простой макрос, который действует как обертка вокруг обычного (defn) макроса и в результате добавляет некоторые метаданные к определенной функции. Поэтому я хотел бы иметь что-то вроде этого:

(defn-plus f [x] (inc x))

... развернуть что-то вроде этого:

(defn #^{:special-metadata :fixed-value} f [x] (inc x))

В принципе это не кажется мне сложным, но у меня возникают проблемы с определением правильности синтаксического анализа [args] и других форм в определенной функции.

В качестве бонуса, если возможно, я бы хотел, чтобы макрос мог обрабатывать все разнородные формы defn (т. Е. С или без строк документации, множественные определения арности и т. Д.). Я видел некоторые вещи в пакете clojure-contrib/def, которые выглядели, возможно, полезными, но было трудно найти пример кода, который их использовал.

1 Ответ

18 голосов
/ 13 июня 2009

Обновлен:

Предыдущая версия моего ответа была не очень надежной. Это выглядит как более простой и правильный способ, украденный у clojure.contrib.def:

(defmacro defn-plus [name & syms]
  `(defn ~(vary-meta name assoc :some-key :some-value) ~@syms))

user> (defn-plus ^Integer f "Docstring goes here" [x] (inc x))
#'user/f
user> (meta #'f)
{:ns #<Namespace user>, :name f, :file "NO_SOURCE_PATH", :line 1, :arglists ([x]), :doc "Docstring goes here", :some-key :some-value, :tag java.lang.Integer}

#^{} и with-meta - это не одно и то же. Объяснение различий между ними см. В обсуждении Рича по списку рассылки Clojure . Это все немного сбивает с толку, и это встречалось много раз в списке рассылки; см. также здесь например.

Обратите внимание, что def - это специальная форма, которая обрабатывает метаданные немного странно по сравнению с некоторыми другими частями языка. Он устанавливает метаданные var, к которым вы def присылаете метаданные символа, который называет var; Я думаю, это единственная причина, по которой все вышеперечисленное работает. См. DefExpr класс в Compiler.java в исходном коде Clojure, если вы хотите увидеть все это.

Наконец, страница 216 из Программирование Clojure говорит:

Как правило, следует избегать макросов считывателей в расширениях макросов, поскольку макросы читателей оцениваются во время чтения, прежде чем начинается раскрытие макросов.

...