Как я могу использовать macroexpand-1 из исходного файла вместо REPL

Как правильно использовать macroexpand-1 для тестирования макросов Clojure, когда вы не работаете в REPL?


person Alan Thompson    schedule 06.05.2017    source источник


Ответы (2)


Проблема в том, что ваши выражения за пределами deftest выполняются во время компиляции, в то время как *ns* связаны, а внутри deftest они выполняются позже, во время выполнения, где *ns* не привязано.

Почему это важно? Потому что macroexpand необходимо разрешить символ iiinc в текущем пространстве имен, чтобы решить, макрос это или нет, и найти его определение, если это макрос, чтобы вызвать его. Итак, вы видите, что ваш macroexpand работает во время компиляции, но не во время выполнения.

Каково решение? Конечно, не запускать тесты во время компиляции! Вместо этого вы должны правильно квалифицировать свои формы в пространстве имен, чтобы они не зависели от удобства времени компиляции *ns*. Вы можете сделать это вручную, написав

(deftest t-stuff
  (println "(macroexpand-1 '(my.ns/iiinc 2))  =>" (macroexpand-1 '(my.ns/iiinc 2)))))

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

(deftest t-stuff
  (println "(macroexpand-1 `(iiinc 2))  =>" (macroexpand-1 `(iiinc 2)))))
person amalloy    schedule 06.05.2017

Предположим, мы хотим протестировать макрос, добавляющий 3 к любому значению:

(defmacro iiinc [x]
  `(+ 3 ~x))

Вместо того, чтобы работать в REPL, я обычно предпочитаю использовать свой любимый текстовый редактор/IDE для разработки кода и использовать плагин lein test-refresh для непрерывного запуска моих модульных тестов. Однако это не работает при попытке использовать macroexpand-1 для итеративной разработки нового макроса.

Проблема, похоже, в некотором конфликте между macroexpand-1 и макросом deftest. Таким образом, решение состоит в том, чтобы избегать использования macroexpand-1 внутри формы (deftest ...). Однако он отлично работает за пределами deftest, даже если он все еще находится в исходном файле модульного теста. Вот пример:

; source file tst.clj.core

(newline)
(println "This works when not in (deftest ...)")
(println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))
    
(deftest t-stuff
  (newline)
  (println "But it is broken inside (deftest ...)")
  (println "(macroexpand-1 '(iiinc 2))  =>" (macroexpand-1 '(iiinc 2)))

  (newline)
  (println "However, we can use the macro itself fine in our tests")
  (println "  (iiinc 2) =>" (iiinc 2))
  (is (= 5 (iiinc 2))))  ; unit test works fine

Результаты вышеизложенного таковы:

This works when not in (deftest ...)
(macroexpand-1 '(iiinc 2))  => (clojure.core/+ 3 2)

Testing tst.clj.core

But it is broken inside (deftest ...)
(macroexpand-1 '(iiinc 2))  => (iiinc 2)

However, we can use the macro itself fine in our tests
  (iiinc 2) => 5

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

Постскриптум

См. следующий ответ для более полного обсуждения того, как написать макрос в Clojure:

Как написать макрос многопоточности Clojure?

person Alan Thompson    schedule 06.05.2017
comment
Хороший ответ, но я думаю, что часть этого контента должна быть в самом вопросе, потому что он объясняет, что вы пытаетесь сделать, но это не работает. Без контекста этого ответа никто другой не смог бы ответить на ваш очень сокращенный вопрос. - person amalloy; 06.05.2017
comment
Оглядываясь назад на это четыре года спустя, я теперь думаю, что весь ответ должен быть перенесен в вопрос. Вы не даете ответа на свой вопрос Как проверить макрос с помощью macroexpand?; вы просто заключаете, что это не работает. Это очень тщательный вопрос, и я думаю, что мой ответ дает на него хороший ответ, я просто не понимаю, как это может быть ответом. - person amalloy; 02.02.2021