Macro function executed twice in SBCL

Discussion of Common Lisp
Post Reply
garethw
Posts: 43
Joined: Fri Jul 13, 2012 12:56 pm
Location: Ottawa, ON

Macro function executed twice in SBCL

Post by garethw » Thu Jan 30, 2014 8:09 pm

Hi all,

I was wondering if someone could explain this SBCL behaviour to me. I have a macro that appears to be executed twice.

First, something that works just fine:

Code: Select all

(defmacro heaven ()
  (print "Everything is fine.")
  100)
=> HEAVEN
(heaven)
"Everything is fine."
=>100
But if I return a quasiquoted lambda, I get the message printed twice:

Code: Select all

(defmacro hell ()
  (print "You can say that again!")
  `(lambda ()
    100))
=> HELL
(hell)
"You can say that again!"
"You can say that again!"
=> #<FUNCTION (LAMBDA ()) {100643AC5B}>
I tried it under LispWorks 6.1.1 and I got what I expected:

Code: Select all

... same def...
(hell)
"You can say that again!" 
=> #<anonymous interpreted function 226EB102>
So I thought I'd step it under SBCL, but that Heisenbugged:

Code: Select all

(step (hell))
"You can say that again!"
=> #<FUNCTION (LAMBDA () :IN #:DROP-THRU-TAG-1) {100670BCAB}>
Or put it in a loop:

Code: Select all

(dotimes (n 5) (hell))
[i]"You can say that again!" [/i]
NIL
What's going on here? Is it something to do with returning the lambda? That it's compiled? This seems too fundamental to be a genuine SBCL bug - and I've tried it with old and brand-new versions (1.0.57, 1.1.15), with the same results.
~garethw

budden73
Posts: 8
Joined: Sat Jan 04, 2014 8:25 am

Re: Macro function executed twice in SBCL

Post by budden73 » Fri Jan 31, 2014 9:09 am

Hi!
Bugs are quite possible in SBCL.
Try putting (break) instead of (print ...) and when (break) invokes a debugger, look at the stack trace.
Do that from SBCL console, not from SLIME. If there are filters on debug frames listed in stack trace,
disable them (read SBCL manual to check for that).

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Macro function executed twice in SBCL

Post by pjstirling » Sun Feb 02, 2014 7:46 am

The short answer is: Don't do that! :)

You have no guarantees on the number of times a macro will be expanded. Implementations are allowed flexibility in the number of times a macro-expander function is executed, interpreter-based implementations/modes only store the raw lists, so under that regime an expander function must be executed for every single time it shows up in the execution trace at runtime

I'm not entirely sure why it is happening, but AFAIR, the repl has some magic where it will interpret some forms rather than compiling-then-invoking, and it might be trying both.

If you try:

Code: Select all

(eval '(hell))
you get two prints, but if you force the interpreter:

Code: Select all

(let ((*evaluator-mode* :interpret)) (eval '(hell)))
then you only get one. Likewise, if you force the compiler:

Code: Select all

(funcall (compile nil '(lambda () (hell)))
you also only get one print.

OTOH it may be a bug, but the code produced AFTER the expansion is only ever being run once, which is the important bit.

Now, back to the general point, relying on side-effects in macro-expander functions is the wrong thing, as I said above the expander may be run all over the place, but also, if your code is compiled and saved to a fasl file, then when you load the fasl into a clean image the expander will never be run at all! I was bitten by this in my html-generation library (which I still need to upload to github :oops:) , I was saving some information about each tag into a hash table from the macro-function and then expecting that data to be available and it wasn't.
I changed the macro to expand into a PROGN that included the necessary SETF (along with the original expansion) and all was well.
Last edited by pjstirling on Sun Feb 02, 2014 8:18 am, edited 1 time in total.

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Macro function executed twice in SBCL

Post by pjstirling » Sun Feb 02, 2014 7:58 am

Just to confirm clisp always expands it, so

Code: Select all

[3]> (dotimes (i 10) (hell))

"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
"You can say that again!" 
NIL
[4]> 

garethw
Posts: 43
Joined: Fri Jul 13, 2012 12:56 pm
Location: Ottawa, ON

Re: Macro function executed twice in SBCL

Post by garethw » Sun Feb 02, 2014 7:46 pm

pjstirling wrote:The short answer is: Don't do that! :)
...
Now, back to the general point, relying on side-effects in macro-expander functions is the wrong thing, as I said above the expander may be run all over the place, but also, if your code is compiled and saved to a fasl file, then when you load the fasl into a clean image the expander will never be run at all! I was bitten by this in my html-generation library (which I still need to upload to github :oops:) , I was saving some information about each tag into a hash table from the macro-function and then expecting that data to be available and it wasn't.
I changed the macro to expand into a PROGN that included the necessary SETF (along with the original expansion) and all was well.
Interesting - that sounds very similar to what I was trying to do. I'll try to see if I can apply your solution to my problem.

Thanks as always for the thoughtful replies.
~garethw

Post Reply