Самый простой способ увидеть, как работает макрос, это проверить его с помощью clojure.core/macroexpand-1
или clojure.walk/macroexpand-all
.
Например, мы можем увидеть, какследующая форма будет расширена:
(cond
(pos? 1) :positive
(neg? -1) :negative)
с macroexpand-1
:
(macroexpand-1
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (clojure.core/cond (neg? -1) :negative))
Мы можем видеть, что когда эта форма раскрывается, clauses
привязывается к последовательности этих выражений: (pos? 1)
, :positive
, (neg? -1)
и :negative
.
(first clauses)
будут иметь значение (pos? 1)
, и его значение будет использоваться в качестве тестового выражения для испускаемого значения if
.Затем макрос проверяет, есть ли у первого предиката требуемое выражение результата, проверяя, имеет ли оно более одного предложения: (next clauses)
оценивается как (:positive (neg? -1) :negative)
, что верно, и истинная ветвь испускаемого if
получит значение(second clauses)
, то есть :positive
.
Другая ветвь испускаемого if
получит (clojure.core/cond (neg? -1) :negative)
.Поскольку переданный код снова будет включать вызов макроса cond
, он будет вызван снова и снова расширен.
Чтобы увидеть полностью развернутый код, мы можем использовать clojure.walk/macroexpand-all
:
(require 'clojure.walk)
(clojure.walk/macroexpand-all
'(cond
(pos? 1) :positive
(neg? -1) :negative))
;; => (if (pos? 1) :positive (if (neg? -1) :negative nil))
Чтобы расширить тему, если формы, включенные в clauses
, оцениваются во время расширения макроса, мы можем добавить некоторые побочные эффекты в код:
(clojure.walk/macroexpand-all
'(cond
(do
(println "(pos? 1) evaluated!")
(pos? 1))
(do
(println ":positive evaluated1")
:positive)
(do
(println "(neg? -1) evaluated!")
(neg? -1))
(do
(println ":negative evaluated!")
:negative)))
=>
(if
(do (println "(pos? 1) evaluated!") (pos? 1))
(do (println ":positive evaluated1") :positive)
(if (do (println "(neg? -1) evaluated!") (neg? -1)) (do (println ":negative evaluated!") :negative) nil))
Как мы видим, побочные эффекты не выполнялись, потому чтони одно из предложений не было оценено во время расширения макроса.
Мы также можем проверить, оценивается ли вызов throw
во время расширения макроса, предоставив clauses
, что приведет к тому, что ветвь else в (if (next clauses) ...
будетПозвонил:
(macroexpand-1 '(cond (pos? 1)))
java.lang.IllegalArgumentException: cond requires an even number of forms
Здесь мы видим, что было сгенерировано исключение, и расширение макроса макроса cond
не завершилось нормально, возвращая код расширенного макроса.Причина, по которой форма throw
вычисляется во время расширения макроса, заключается в том, что она не заключена в кавычки (например, `` (throw ...) `).