Я хотел бы написать макрос Clojure with-test-tags
, который обертывает кучу форм и добавляет некоторые метаданные к имени каждой формы deftest
- в частности, добавляет некоторые вещи в ключ :tags
так что я могу играть с инструментом для запуска тестов с определенным тегом.
Одной очевидной реализацией для with-test-tags
является рекурсивное перемещение всего тела, изменение каждой формы deftest
, как я ее нахожу. Но я недавно читал Let Over Lambda, и он делает хороший вывод: вместо того, чтобы просто пропустить код, просто оберните код в macrolet
и дайте компилятору пройти его для вас. Что-то вроде:
(defmacro with-test-tags [tags & body]
`(macrolet [(~'deftest [name# & more#]
`(~'~'deftest ~(vary-meta name# update-in [:tags] (fnil into []) ~tags)
[email protected]#))]
(do [email protected])))
(with-test-tags [:a :b]
(deftest x (...do tests...)))
Это имеет очевидную проблему, однако, что макрос deftest
продолжает рекурсивно расширяться навсегда. Я мог бы расширить его до clojure.test/deftest
, избегая при этом каких-либо дальнейших рекурсивных расширений, но тогда я не могу с пользой вставить экземпляры with-test-tags
для подписи подгрупп тестов.
В этот момент, особенно для чего-то простого, как deftest
, похоже, что ходьба кода сама по себе будет проще. Но мне интересно, знает ли кто-нибудь технику написания макроса, который "слегка изменяет" некоторые подвыражения, без повторения навсегда.
Для любопытных: я рассмотрел некоторые другие подходы, такие как наличие времени binding
-able var, которое я установил при прохождении вверх и вниз по коду, и используя этот var, когда я наконец увижу deftest
, но поскольку каждый макрос возвращает только одно расширение, его привязки не будут доступны для следующего вызова макроэкспонента.
Изменить
Я выполнил реализацию postwalk только сейчас, и, хотя он работает, он не соблюдает специальные формы, такие как 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)))
(Кроме того, извините за возможные помехи в теге common- lisp – я подумал, что вы можете помочь с более сложными макросами даже при минимальном опыте Clojure.)