1

I've been trying to write a recursive loop in clojure that will print me out the very last number in the list. The point is not that I need to get the last number (for which I'm sure there's a built in function for that) but that I want to better understand recursion and macros in clojure. So I have this macro...

(defmacro loop-do [the-list]
  `(if (= (count '~the-list) 1)
       (println (first '~the-list))
       (loop-do (rest '~the-list))))

But I get a stackoverflow error. What am I doing wrong?

5
  • Take a look at the loop/recur functions. Commented Jan 9, 2018 at 14:55
  • at this line: (loop-do (rest '~the-list)) you pass to the macro the following list literally: (rest ...) so you don't reduce the data size on every step. Instead you enlarge the list )) Commented Jan 9, 2018 at 15:30
  • But how is rest enlarging the list? I thought if I passed '(1 2 3 4 5) that it would just return everything other then the first element and pass that into loop-do? Commented Jan 9, 2018 at 15:56
  • for which I'm sure there's a built in function for that: yes, last Commented Jan 9, 2018 at 16:07
  • It enlarges a leetwinski says because when you call your macro as (loop-do (rest '~the-list)), you're passing the list of unevaluated symbols ((rest '~the-list)) back to the macro. (rest '~the-list) isn't evaluated before being given to the macro. If you want practical practice with macros, you should try doing tasks that actually require macros. Try rewriting core macros. Commented Jan 9, 2018 at 16:16

1 Answer 1

2

How will people use your macro?

Somewhere, someone will call:

(loop-do list)

As a piece of code, those are only two symbols in a list. The first one is recognized as your macro, and the second one, list, is a symbol that represents a variable that will be bound at runtime. But your macro only knows that this is a symbol.

The same goes for:

(loop-do (compute-something))

The argument is a form, but you do not want to get the last element of that form, only the last element of the list obtained after evaluating the code.

So: you only know that in your macro, the-list will be bound to an expression that, at runtime, will have to be a list. You cannot use the-list as-if it was a list itself: neither (count 'list) nor (count '(compute-something)) does what you want.

You could expand into (count list) or (count (compute-something)), though, but the result would only be computed at runtime. The job of the macro is only to produce code.

Recursive macros

Macros are not recursive: they expand into recursive calls.

(and a b c)

might expand as:

(let [a0 a] (if a0 a0 (and b c)))

The macroexpansion process is a fixpoint that should terminate, but the macro does not call itself (what would that mean, would you expand the code while defining the macro?). A macro that is "recursive" as-in "expands into recursive invocations" should have a base case where it does not expand into itself (independently of what will, or will not, happen at runtime).

(loop-do x)

... will be replaced by:

(loop-do (rest 'x))

... and that will be expanded again. That's why the comments say the size actually grows, and that's why you have a stackoverflow error: macroexpansion never finds a fixpoint.

Debugging macros

You have a stackoverflow error. How do you debug that?

Use macroexpand-1, which only performs one pass of macroexpansion:

(macroexpand-1 '(loop-do x))
=> (if (clojure.core/= (clojure.core/count (quote x)) 1)
       (clojure.core/println (clojure.core/first (quote x)))
       (user/loop-do (clojure.core/rest (quote x))))

You can see that the generated code still contains a call to usr/loop-do , but that the argument is (clojure.core/rest (quote x)). That's the symptom you should be looking for.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah I see! This is very clear! Thank you for the concise but detailed explanation! :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.