2

This is a related question, some sort of a follow up.

Let's say I'm trying to build a loop expression by using macros, in which the resulting loop expression is dependant on whether the parameter is a list:

(defmacro testing-loop (var)
   `(eval (append '(loop for x from 0 to 5)
            (when (consp ,var) '(and y in ,var))            
            '(collect)            
            (if (consp ,var) '(y) '(x))))

This seems to work:

CL-USER> (testing-loop 2)
(0 1 2 3 4 5)
CL-USER> (testing-loop (list 5 6 7))
(5 6 7)

But when applying this macro in a lexical closure, it breaks down:

CL-USER> (let ((bar (list 1 2 3)))
           (testing-loop bar))

which throws undefined variable: BAR

I expected testing-loop to macroexpand into the lexical scope where bar is bound?

3
  • 1
    You have a call to EVAL there. EVAL always evaluates the form in the null lexical environment. Frankly, I don't really understand why you have the eval there in the first place. Commented Feb 10, 2012 at 4:32
  • @Elias Mårtenson: I think the eval was suggested to mck in the answers to the previous question. @mck: if you're trying to learn about macros by trying to figure out use cases for them and then find solutions for those use cases, maybe you should read OnLisp and/or Let Over Lambda. They cover a lot of interesting ground - I recommend starting with OnLisp, though. Commented Feb 10, 2012 at 6:35
  • @EliasMårtenson: Thanks for the comment, I didn't know eval evaluates in the null lexical environment; learned something new. However, if I don't put eval there, the macro returns the list of the code. What am I doing wrong? Commented Feb 10, 2012 at 16:54

1 Answer 1

4

@mck, I see why you want to use eval now. But it's a very messy solution, and slow, as I mentioned in my answer to the previous question. The classic On Lisp says this about eval:

"Generally it is not a good idea to call eval at runtime, for two reasons:

  1. It’s inefficient: eval is handed a raw list, and either has to compile it on the spot, or evaluate it in an interpreter. Either way is slower than compiling the code beforehand, and just calling it.

  2. It’s less powerful, because the expression is evaluated with no lexical context. Among other things, this means that you can’t refer to ordinary variables visible outside the expression being evaluated.

Usually, calling eval explicitly is like buying something in an airport gift-shop. Having waited till the last moment, you have to pay high prices for a limited selection of second-rate goods."

In this case the simplest thing is just to:

(defmacro testing-loop (var)
  (let ((g (gensym)))
   `(let ((,g ,var))
      (if (consp ,g)
        (loop for x from 0 to 5 collect x)
        (loop for x from 0 to 5 and y in ,g collect y)))))

I know you want to factor out the common loop for x from 0 to 5 (which isn't actually needed in the second branch anyways). But loop is itself a macro which is converted at compile time to efficient, low level code. So the call to loop has to be built at compile time, using values which are available at compile time. You can't just insert an (if) in there which is intended to be evaluated at run time.

If you really don't want to repeat loop for x from 0 to 5, you could do something like:

(let ((a '(loop for x from 0 to 5)))
  `(if (consp ,var)
       (,@a collect x)
       (,@a and y in ,var collect y)))

That's just to give you the idea; if you really do this, make sure to gensym!

A good lesson to learn from this is: when you are writing macros, you need to keep clearly in mind what is happening at compile time and what is happening at run time. The macro you wrote with eval compiles the loop macro dynamically, every time it is run, based on the return value of consp. You really want to compile the 2 different loop macros once, and just select the correct one at run-time.

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

3 Comments

Thank you so much for the terrific answer!
Alex, if I'm not mistaken, fexprs allow the IF to be done at expansion time yes?
@PaulNathan, mck wants the loop which is executed to be determined by the run-time value of an expression. If you look back at the "related question", that will give you an idea what he is trying to do.

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.