Problem with LFE macro

Hello.

I’m having a problem with macro expansion.
This is my macro:

lfe> (defmacro foo (body)
       `(progn
          ,@body))

lfe> (foo (io:format "hello~n"))
** exception error: symbol io:format is unbound

Now when looking at the expansion it gets clear why it doesn’t work:

lfe> (macroexpand '(foo (io:format "hello~n")) $ENV)
(progn io:format "hello~n")

But I don’t know what I did wrong. The macro seems OK to me.
Can anyone spot the issue?

Manfred

3 Likes

No LFE or lisp expert here, but I was interested : )

If you wrap your args twice in parens, it’ll work. This makes sense because :

This does not work.

lfe> (progn io:format "hello~n")
** exception error: symbol io:format is unbound

But this works :

(progn (io:format "hello~n"))
hello
ok

Thus,

lfe> (defmacro foo (body) `(progn ,@body))
foo
lfe> (foo ((io:format "hello~n")))
hello
ok

Without the double parens and by the time @body is referred to in the macro it is the equivalent of

lfe> io:format "hello~n"
** exception error: symbol io:format is unbound

It’s not clear to me how to wrap @body in the macro with parens so you could avoid doing it in the call to foo though.

You could also simply do :

lfe> (defmacro foo (body) body))
foo
lfe> (foo (io:format "hello~n"))
hello
ok

But there may be problems with that at compile time?

I would ask @rvirding or @oubiwann for a technical explanation on this one :slight_smile:

2 Likes

Thanks for responding.

I know from Common Lisp, that the &rest, or &body arguments in macros automatically add extra parens around the argument value, which are then again removed by the splice. Because the body could consist of multiple forms/sexprs.
However, this doesn’t seem to be the case here. It should IMO not ne necessary to manually wrap it in more parens.

2 Likes

There are a couple of things which cause this problem:

  • progn takes a list of forms to evaluate and as your expansion expanded to

(progn io:format “hello~n”)`

then it started evaluating each form one after the other. So first io:format which is an unbound symbol.

  • It’s in the definition of the macro and and in ,@body. When you define the macro as (defmacro foo (body) ...) you are saying that the macro when called should get one and only on argument and body will refer to that argument. So when you call (foo (io:format "hello~n")) then body will get the value (io:format "hello~n"). Now in a backquote using ,@ to expand a value will splice that list into the expansion which is why you got the flattened progn. This is how CL defines the backquote and how it expands.

So if you define the macro as

(defmacro foo body `(progn ,@body))

then body will refer to the list of all arguments and the expansion will become as you wished it to be.

lfe> (defmacro foo body `(progn ,@body))
foo
lfe> (foo (io:format “hello~n”))
hello
ok
lfe> (macroexpand '(foo (io:format “hello~n”)) $ENV)
(progn (io:format “hello~n”))
lfe> (foo (io:format “hello~n”) (io:format “there~n”))
hello
there
ok
lfe> (macroexpand '(foo (io:format “hello~n”) (io:format “there~n”)) $ENV)
(progn (io:format “hello~n”) (io:format “there~n”))

If you look in the doc/lfe_guide.txt in the Macros section it describes this and gives an example which is close to your macro.

2 Likes

OK, thanks for your response.
I was actually looking through the documentation. Now that you say it I looked again and found this one:

(defmacro my-list args
  "List of arguments."
  `(list ,@args))

Which indeed looks like what I need. But I guess the argument without parenthesis got me distracted and I thought: that cannot be what I need.
So effectively a single argument without parenthesis represents a list of forms as body similar as the &rest, or &body arguments in CL.
Excellent I will remember this.

3 Likes