In an attempt to better learn macros I've been playing around with a few simple examples including recreating a simplified thread-last. I'm having trouble understanding why one version below results in a stack-overflow and the other doesn't.
;; 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))
My initial reaction was that it must be due to the fact that in the first example, although the resulting expansion looks as if it would run just fine if it were normal code, since it's a macro the recursive call continually expands and the if test never happens. In the second version, the if test happens before any list is returned for run-time evaluation giving a chance to break out.
However, I'm not sure if this mental model is correct because the following example (Clojure Brave & True) looks rather similar to the first version above, and works fine:
(defmacro our-and
([] true)
([x] x)
([x & next]
`(if ~x (our-and ~@next) ~x)))
EDIT: To clarify I mean that the above our-and
is similar structurally (not semantically) in that it returns a list which contains the recursive call to the macro, similar to version one of my thread-last replica above.