В попытке лучше изучить макросы я поиграл с несколькими простыми примерами, включая воссоздание упрощенного последнего потока. У меня возникают проблемы с пониманием того, почему одна из приведенных ниже версий приводит к переполнению стека, а другая - нет.
;; version one - blows up w/ stack overflow
(defmacro ->>> [e1 & exprs]
`(if ~exprs
(->>> ~(concat (first exprs) (list e1)) ~@(rest exprs))
~e1))
;; version two, works just fine
(defmacro ->>> [e1 & exprs]
(if exprs
`(->>> ~(concat (first exprs) (list e1)) ~@(rest exprs))
e1))
Моя первоначальная реакция состояла в том, что это должно быть связано с тем, что в первом примере, хотя полученное расширение выглядит так, как будто оно будет работать нормально, если бы это был обычный код, поскольку это макрос, рекурсивный вызов постоянно расширяется, и если тест никогда не произойдет. Во второй версии тест if выполняется до того, как какой-либо список будет возвращен для оценки во время выполнения, что даст возможность выйти из него.
Однако я не уверен, верна ли эта ментальная модель, потому что следующий пример (Clojure Brave & True) выглядит довольно похоже на первую версию выше и работает нормально:
(defmacro our-and
([] true)
([x] x)
([x & next]
`(if ~x (our-and ~@next) ~x)))
EDIT : Чтобы уточнить, я имею в виду, что вышеприведенный our-and
аналогичен структурно (не семантически) в том, что он возвращает список, который содержит рекурсивный вызов макроса, аналогично первой версии моего потока последняя копия выше.