Возможно ли создание "прозрачного" макролета?

Я бы хотел написать макрос Clojure с тегами-тегами , который обертывает кучу форм и добавляет некоторые метаданные к имени каждой формы deftest - в частности, добавить что-то к ключу :tags, чтобы я мог поиграть с инструментом для запуска тестов с определенным тегом.

Одна из очевидных реализаций для с тегами-тегами - это прохождение всего тела рекурсивно, модификация каждой формы deftest так, как я нахожу. Но я недавно читал Let Over Lambda, и он делает хорошее замечание: вместо того, чтобы самому ходить по коду, просто оберните код в макролет и позвольте компилятору ходить по нему за вас. Что-то вроде:

(defmacro with-test-tags [tags & body]
  `(macrolet [(~'deftest [name# & more#]
                `(~'~'deftest ~(vary-meta name# update-in [:tags] (fnil into []) ~tags)
                   ~@more#))]
     (do ~@body)))

(with-test-tags [:a :b] 
  (deftest x (...do tests...)))

Однако, есть очевидная проблема, что макрос deftest продолжает рекурсивно расширяться навсегда. Вместо этого я мог бы заставить его расширяться до clojure.test/deftest, избегая тем самым дальнейших рекурсивных расширений, но тогда я не смогу с пользой вложить экземпляры с тегами-тегами для обозначения подгрупп тестов.

На данный момент, особенно для такого простого, как deftest, похоже, что ходить по коду самому будет проще. Но интересно, знает ли кто-нибудь технику написания макроса, который "слегка модифицирует" определенные подвыражения, не повторяясь вечно.

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

Правка

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

(defmacro with-test-tags [tags & body]
  (cons `do
        (postwalk (fn [form]
                    (if (and (seq? form)
                             (symbol? (first form))
                             (= "deftest" (name (first form))))
                      (seq (update-in (vec form) [1]
                                      vary-meta update-in [:tags] (fnil into []) tags))
                      form))
                  body)))

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

15
задан Svante 30 August 2011 в 10:18
поделиться