Say, I have the macros outer and inner, and I write the following:
(outer 10 (inner +1))
Is it specified, in which order outer and inner are expanded? I see
that in sbcl outer is expanded before inner, and I can do the following:
(let (store)
(defmacro outer (val &body body) (setf store val) `(progn ,@body))
(defmacro inner (expr) `(,expr ,store)))
That is, store is first setf'd by outer, then read by inner.
Is this implementation specific, or can I rely on this?
If I have missed the obvious in the hyperspec, I'd be grateful for the
pointer.
Thanks,
Peter
Peter Hildebrandt <·················@gmail.com> writes:
> Is it specified, in which order outer and inner are expanded?
It's outer that is reliably expanded first. This follows from an
understanding that eval itself is defined as an operation on an
expression (not a subpart of it) and that operation is described
recursively from the outside in:
| 3.1.2 The Evaluation Model
| http://www.lispworks.com/documentation/HyperSpec/Body/03_ab.htm
|
| A Common Lisp system evaluates forms with respect to lexical,
| dynamic, and global environments. The following sections describe
| the components of the Common Lisp evaluation model.
|
| 3.1.2.1 Form Evaluation
| http://www.lispworks.com/documentation/HyperSpec/Body/03_aba.htm
|
| Forms fall into three categories: symbols, conses, and self-evaluating
| objects.
|
| 3.1.2.1.2 Conses as Forms
| http://www.lispworks.com/documentation/HyperSpec/Body/03_abab.htm
|
| A cons that is used as a form is called a compound form.
|
| If the car of that compound form is a symbol, that symbol is the name
| of an operator, and the form is either a special form, a macro form,
| or a function form, depending on the function binding of the operator
| in the current lexical environment.
|
| 3.1.2.1.2.2 Macro Forms
| http://www.lispworks.com/documentation/HyperSpec/Body/03_ababb.htm
|
| If the operator names a macro, its associated macro function is
| applied to the entire form and the result of that application is used
| in place of the original form.
But if you did not know this, you could mostly have inferred it from
design constraints:
The fact that outer is a macro means a program. Lisp cannot know what
this program will return without running it. Hence, as a consequence,
Lisp cannot know whether inner will still even be in the expansion or,
importantly, whether if it is it will be quoted. In both cases
(omission and quotation), you would not want to run the inner macro.
And, even beyond that, macros take as an argument a representation of
the lexical environment in which they are being expanded [see
&environment], and there is no such environment available until
expansion has been done. Consequently, all of these factors mean that
the design is necessarily such that outer must be expanded to reach
inner.
[1] In the most general case, it can actually be both, since the
expansion can inject the identical (i.e., EQ) expression into both
evaluated and non-evaluated contexts.
> Is it specified, in which order outer and inner are expanded? I see
> that in sbcl outer is expanded before inner, and I can do the
> following:
>
> (let (store)
> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
> (defmacro inner (expr) `(,expr ,store)))
>
> That is, store is first setf'd by outer, then read by inner.
>
> Is this implementation specific, or can I rely on this?
You can rely on the notion that outer expansion will occur before inner
expansion. However, I would not rely on the other things you're doing here.
The only thing you can be sure of is that the inner cannot be lexically
analyzed until the outer has been executed.
Note well that you cannot be sure that the compiler has not, for
whatever reason, called some other expansion of outer in the interim.
You don't show your calling code here, but for example [2]:
(outer 3 (outer 4 (inner identity)) (inner identity))
will probably have not so good effects for you (assuming I got the uses
of the args right at all).
Also, there are cases where the value may surprise you because the
expansion is allowed to be incremental/lazily in non-compiled code
(there are some additional constraints in compiled code, but they
still may not be firm enough for your needs--I didn't think them
through carefully--see "minimal compilation") and that means that,
since the avenue of communication you're using assumes a definite
left-to-right and top-to-bottom UNINTERRUPTED nature (not in the
process interrupt sense, but in the sense of contiguous action even as
a synchronous activity), you're likely to be surprised if the code
executes one branch of an IF within the OUTER and expands things one
way (leaving the other branch unexecuted and hence unexpanded) and
then later, on a different call to the function executes the other
branch, there will be [potentially] a different dynamically previous
call to OUTER such that within the expression, the expansions won't
even appear consistent.
Even in compiled code, I can think of various ways in which there are
issues I'd want to think hard about before relying on this strategy,
and I'd say you are leaning too heavily on things not guaranteed to
work for my comfort. The easiest example to see is that if you have a
multi-tasking Lisp, you haven't made your code threadsafe, so two
compilations co-occurring can clobber one another. (An example
involving an actual interrupt which happens to compile a call to the
same macro at an inopportune time even in a non-multitasking Lisp is
theoretically possible to construct, but is not likely to ever happen,
so I won't lead with that.)
Basically, although you've done the necessary bow to the god of
lexical scoping by putting a let around that, s/he isn't going to
protect you because you've not done the work to make there be more
than one store, nor to assure that these macros get USED in lockstep.
Left-to-right/inner-outer is not your enemy here... the enemy is that
this is only a partial order and that other programs have other needs
that will not perturb the partial order but could do damage to your
storage medium.
The correct general shape for a properly cooperative program of the
kind I think you want to write is (I think [2]):
(defmacro outer (val &body body)
(macrolet ((inner (expr) `(,expr ',val)))
,@body))
[2] I'm in a hurry so am just dashing this all off rather quickly, and
didn't test any of it. Caveat emptor. At least it gives you something
to think about. :)
Kent just sent me the following via email and asked me to post it as a
correction to his former post.
--------------------------------------------------------
In the post this morning I sent, I suggested
(defmacro outer (val &body body)
(macrolet ((inner (expr) `(,expr ',val)))
,@body))
As I mentioned in the post, I was in a hurry to get out the door and
didn't test it. And it was wrong. Now I'm not where I can do news
posting, so I'm resorting to email. Hope you don't mind. In fact of
course, this will need two levels of backquoted. Probably something
more like:
(defmacro outer (val &body body)
`(macrolet ((inner (operator) `(,operator ',',val)))
,@body))
Note that I renamed the variable name in the macrolet only
because the thing you're supposed to pass there is not a
'normal' expression--it needs to work in operator position,
so it's best to emphasize that.
So you can do:
(outer 3 (list 'result (inner 1+)))
=> (RESULT 4)
(pprint (macroexpand-1 '(outer 3 (list 'result (inner 1+)))))
(MACROLET ((INNER (OPERATOR) `(,OPERATOR '3)))
(LIST 'RESULT (INNER 1+)))
[You're welcome to post this text to the newsgroup as a correction
if you're of a mind to. If neither you nor anyone else has
volunteered a correction by time I'm back to my newsreader, I'll
do it myself.]
Good luck, and sorry for the confusion.
--Kent
Kent M Pitman wrote:
> Peter Hildebrandt <·················@gmail.com> writes:
>
>> Is it specified, in which order outer and inner are expanded?
>
> It's outer that is reliably expanded first. This follows from an
> understanding that eval itself is defined as an operation on an
> expression (not a subpart of it) and that operation is described
> recursively from the outside in:
>
> | 3.1.2 The Evaluation Model
> | http://www.lispworks.com/documentation/HyperSpec/Body/03_ab.htm
> |
> | A Common Lisp system evaluates forms with respect to lexical,
> | dynamic, and global environments. The following sections describe
> | the components of the Common Lisp evaluation model.
> |
> | 3.1.2.1 Form Evaluation
> | http://www.lispworks.com/documentation/HyperSpec/Body/03_aba.htm
> |
> | Forms fall into three categories: symbols, conses, and self-evaluating
> | objects.
> |
> | 3.1.2.1.2 Conses as Forms
> | http://www.lispworks.com/documentation/HyperSpec/Body/03_abab.htm
> |
> | A cons that is used as a form is called a compound form.
> |
> | If the car of that compound form is a symbol, that symbol is the name
> | of an operator, and the form is either a special form, a macro form,
> | or a function form, depending on the function binding of the operator
> | in the current lexical environment.
> |
> | 3.1.2.1.2.2 Macro Forms
> | http://www.lispworks.com/documentation/HyperSpec/Body/03_ababb.htm
> |
> | If the operator names a macro, its associated macro function is
> | applied to the entire form and the result of that application is used
> | in place of the original form.
>
> But if you did not know this, you could mostly have inferred it from
> design constraints:
>
> The fact that outer is a macro means a program. Lisp cannot know what
> this program will return without running it. Hence, as a consequence,
> Lisp cannot know whether inner will still even be in the expansion or,
> importantly, whether if it is it will be quoted. In both cases
> (omission and quotation), you would not want to run the inner macro.
> And, even beyond that, macros take as an argument a representation of
> the lexical environment in which they are being expanded [see
> &environment], and there is no such environment available until
> expansion has been done. Consequently, all of these factors mean that
> the design is necessarily such that outer must be expanded to reach
> inner.
>
> [1] In the most general case, it can actually be both, since the
> expansion can inject the identical (i.e., EQ) expression into both
> evaluated and non-evaluated contexts.
>
>> Is it specified, in which order outer and inner are expanded? I see
>> that in sbcl outer is expanded before inner, and I can do the
>> following:
>>
>> (let (store)
>> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
>> (defmacro inner (expr) `(,expr ,store)))
>>
>> That is, store is first setf'd by outer, then read by inner.
>>
>> Is this implementation specific, or can I rely on this?
>
> You can rely on the notion that outer expansion will occur before inner
> expansion. However, I would not rely on the other things you're doing here.
>
> The only thing you can be sure of is that the inner cannot be lexically
> analyzed until the outer has been executed.
>
> Note well that you cannot be sure that the compiler has not, for
> whatever reason, called some other expansion of outer in the interim.
> You don't show your calling code here, but for example [2]:
> (outer 3 (outer 4 (inner identity)) (inner identity))
> will probably have not so good effects for you (assuming I got the uses
> of the args right at all).
>
> Also, there are cases where the value may surprise you because the
> expansion is allowed to be incremental/lazily in non-compiled code
> (there are some additional constraints in compiled code, but they
> still may not be firm enough for your needs--I didn't think them
> through carefully--see "minimal compilation") and that means that,
> since the avenue of communication you're using assumes a definite
> left-to-right and top-to-bottom UNINTERRUPTED nature (not in the
> process interrupt sense, but in the sense of contiguous action even as
> a synchronous activity), you're likely to be surprised if the code
> executes one branch of an IF within the OUTER and expands things one
> way (leaving the other branch unexecuted and hence unexpanded) and
> then later, on a different call to the function executes the other
> branch, there will be [potentially] a different dynamically previous
> call to OUTER such that within the expression, the expansions won't
> even appear consistent.
>
> Even in compiled code, I can think of various ways in which there are
> issues I'd want to think hard about before relying on this strategy,
> and I'd say you are leaning too heavily on things not guaranteed to
> work for my comfort. The easiest example to see is that if you have a
> multi-tasking Lisp, you haven't made your code threadsafe, so two
> compilations co-occurring can clobber one another. (An example
> involving an actual interrupt which happens to compile a call to the
> same macro at an inopportune time even in a non-multitasking Lisp is
> theoretically possible to construct, but is not likely to ever happen,
> so I won't lead with that.)
>
> Basically, although you've done the necessary bow to the god of
> lexical scoping by putting a let around that, s/he isn't going to
> protect you because you've not done the work to make there be more
> than one store, nor to assure that these macros get USED in lockstep.
> Left-to-right/inner-outer is not your enemy here... the enemy is that
> this is only a partial order and that other programs have other needs
> that will not perturb the partial order but could do damage to your
> storage medium.
>
> The correct general shape for a properly cooperative program of the
> kind I think you want to write is (I think [2]):
>
> (defmacro outer (val &body body)
> (macrolet ((inner (expr) `(,expr ',val)))
> ,@body))
>
>
> [2] I'm in a hurry so am just dashing this all off rather quickly, and
> didn't test any of it. Caveat emptor. At least it gives you something
> to think about. :)
>
In article <·············@nhplace.com>,
Kent M Pitman <······@nhplace.com> wrote:
> Peter Hildebrandt <·················@gmail.com> writes:
>
> > Is it specified, in which order outer and inner are expanded?
>
> It's outer that is reliably expanded first. This follows from an
> understanding that eval itself is defined as an operation on an
> expression (not a subpart of it) and that operation is described
> recursively from the outside in:
More informally, it follows from the general idea that the big
difference between macros and functions is that function arguments are
automatically evaluated, while macro arguments are not. And expanding
macros is part of that automatic evaluation.
The "arguments" to macros don't even have to be valid function or macro
calls, and in many cases they aren't. Consider:
(defmacro inner ...)
(with-open-file (inner "filename")
(read inner))
In the second expression, INNER is a variable being bound, and it would
be very strange if (inner "filename") were expanded as a macro.
--
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
Kent,
thank you for the thorough analysis. c.l.l amazes me anew every time I
ask a question. In the beginning I am just after a simple yes or no,
and soon I learn things about lisp I would never have thought about.
Kent M Pitman wrote:
> It's outer that is reliably expanded first. This follows from an
> understanding that eval itself is defined as an operation on an
> expression (not a subpart of it) and that operation is described
> recursively from the outside in:
[snip]
I guessed so, but I did not think of the right terms to look it up.
Thanks, this was very enlightening reading.
> But if you did not know this, you could mostly have inferred it from
> design constraints:
>
> The fact that outer is a macro means a program. Lisp cannot know what
> this program will return without running it. Hence, as a consequence,
> Lisp cannot know whether inner will still even be in the expansion or,
> importantly, whether if it is it will be quoted. In both cases
> (omission and quotation), you would not want to run the inner macro.
> And, even beyond that, macros take as an argument a representation of
> the lexical environment in which they are being expanded [see
> &environment], and there is no such environment available until
> expansion has been done. Consequently, all of these factors mean that
> the design is necessarily such that outer must be expanded to reach
> inner.
Again, I had an intuition it should be so, but I failed to come up with
a logic argumenmt. Now this is crystal clear.
>> Is this implementation specific, or can I rely on this?
>
> You can rely on the notion that outer expansion will occur before inner
> expansion. However, I would not rely on the other things you're doing here.
And this was the most important part. While I had an intuition that it
should work in principal, it left a bad taste. That is why I posted here.
> The only thing you can be sure of is that the inner cannot be lexically
> analyzed until the outer has been executed.
>
> Note well that you cannot be sure that the compiler has not, for
> whatever reason, called some other expansion of outer in the interim.
> You don't show your calling code here, but for example [2]:
> (outer 3 (outer 4 (inner identity)) (inner identity))
> will probably have not so good effects for you (assuming I got the uses
> of the args right at all).
My actual code is a little more complex than the toy example I posted.
The arguments to outer are named, outer uses those names to store
interims values in store, which is actually a hash table, and inner
takes the names supplied to outer and computes its expansion as a
hashtable lookup.
So this would not break my code, but macrolet is really what I want here.
> Also, there are cases where the value may surprise you because the
> expansion is allowed to be incremental/lazily in non-compiled code
[snip]
> Even in compiled code, I can think of various ways in which there are
> issues I'd want to think hard about before relying on this strategy,
> and I'd say you are leaning too heavily on things not guaranteed to
> work for my comfort.
[snip]
Again, thanks for providing this wonderful explanation. I felt what I
was doing was not quite kosher, but could not point my finger
at it. This is exactly it. Now I have a good reason to get rid of this
ugly hashtable hack.
> The correct general shape for a properly cooperative program of the
> kind I think you want to write is (I think [2]):
>
> (defmacro outer (val &body body)
> (macrolet ((inner (expr) `(,expr ',val)))
> ,@body))
Yep, that's it. (+ your corrections from your email). This is exactly
the way to go.
Again, thank you for your insight!
Peter
Peter Hildebrandt wrote:
> Kent M Pitman wrote:
>
>> The correct general shape for a properly cooperative program of the
>> kind I think you want to write is (I think [2]):
>>
>> (defmacro outer (val &body body)
>> `(macrolet ((inner (operator) `(,operator ',',val)))
>> ,@body))
[I have diabolically spliced in the corrected version. ed]
>
>
> Yep, that's it. (+ your corrections from your email). This is exactly
> the way to go.
<cough> Sorry, I missed this originally since it was not about Arc, but
there might be another way to go. It depends on the real code, which
this NG should have beaten out of you, NDA or no. Not sure it is better,
but in one word: variable capture.
(defmacro cll-outer (val &body body)
`(let ((outer-val ,val))
,@body))
(defmacro cll-inner (expr)
`(,expr outer-val))
(export! cll-outer cll-inner) ;; my own little exporter
(cll-outer 10 (cll-inner 1+))
-> 11
Yeah! Now what if someone does this?:
(cll-outer 10 (let ((outer-val 20)) (cll-inner 1+)))
-> 21
(a) Fire them. If it is yourself and that is not an option, define the
macros in a weird separate package and then move to a different one:
(cll-outer 10 (let ((outer-val 20)) (cll-inner 1+)))
-> 11
Compiled you should get a warning on outer-val never being used, which
might help you realize the error of your ways in case you were trying to
get 21. (In my case had I really really wanted to I would:
(cll-outer 10 (let ((cells::outer-val 20)) (cll-inner 1+)))
-> 21
btw, this is how Cells used to work until JP Massar opened my eyes to
the genius of dynamic binding: the C? rule wrapper set up the variable
SELF and then the dependency-generating macros (^my-slot other-thing)
invisibly snagged the value of SELF to know who was asking (without
cluttering up the code with a lot of plumbing). Note then that (my-slot
other-thing) (the plain reader method) /did not/ generate a dependency,
and conversely it was a total PITA to call a function and get
dependencies generated on (^my-slot xx) forms therein. Switching to a
dynamic binding of a hidden *caller-stack* (so I can detect cycles) thus
let me dump the ^stuff /and/ be guaranteed that any value accessed would
become a dependency however it got accessed. But I digress.
Call me psychic, but I guess you need this to create a nice lispy
wrapper for some huge C library. My further guess is that you'll just
need a few of these sneaky variables, just as I just needed 'self to do
Cells early on. Now in my case I wanted to export 'self because the
client code would want access to it as well, but it was no hardship
having to remember not to:
(c? (let self 42
(+ self 10)))
Oh, sorry, that is Arc. Anyway, you get the idea: if your lispy wrapper
has a few key variables you want avoid reiterating all over the map,
just go with variable capture. Either do not export the symbols, or, if
users will need to get at them, export them and let them no not to be
silly about binding them to 42.
hth, kenny
--
http://www.theoryyalgebra.com/
"In the morning, hear the Way;
in the evening, die content!"
-- Confucius
Ken Tilton wrote:
> <cough> Sorry, I missed this originally since it was not about Arc, but
> there might be another way to go. It depends on the real code, which
> this NG should have beaten out of you, NDA or no. Not sure it is better,
> but in one word: variable capture.
Thought about it before. I really should have been more exact.
So, there is one macro, and (Kenny is truly psychic) it is used to call
a CFFI wrapped function and do some type juggling. Just use (call-fn fn
var1 type1 var2 type2 result-type) and get the result back, properly typed.
Since there might be some preparation involved (like, allocating foreign
memory, type and other conversions), this expands in something like
(with-return (result-type)
(with-var (var1 type1)
(with-var (var2 type2)
(fn (process var1) (process var2)))))
So, yes, the order of the with's and the process's (outer and inner) is
well defined since the code is auto generated.
> (defmacro cll-outer (val &body body)
> `(let ((outer-val ,val))
> ,@body))
>
> (defmacro cll-inner (expr)
> `(,expr outer-val))
>
> (export! cll-outer cll-inner) ;; my own little exporter
> (cll-outer 10 (cll-inner 1+))
> -> 11
I thought of that, too, but then decided I did not like it and rather
have outer make a gensym and have inner use that.
> Yeah! Now what if someone does this?:
>
> (cll-outer 10 (let ((outer-val 20)) (cll-inner 1+)))
> -> 21
>
> (a) Fire them. If it is yourself and that is not an option, define the
> macros in a weird separate package and then move to a different one:
>
> (cll-outer 10 (let ((outer-val 20)) (cll-inner 1+)))
> -> 11
>
> Compiled you should get a warning on outer-val never being used, which
> might help you realize the error of your ways in case you were trying to
> get 21. (In my case had I really really wanted to I would:
>
> (cll-outer 10 (let ((cells::outer-val 20)) (cll-inner 1+)))
> -> 21
This is a nice way to hack around variable capture :)
[good stuff about cells internals snipped]
> Call me psychic, but I guess you need this to create a nice lispy
> wrapper for some huge C library. My further guess is that you'll just
> need a few of these sneaky variables, just as I just needed 'self to do
> Cells early on. Now in my case I wanted to export 'self because the
> client code would want access to it as well, but it was no hardship
> having to remember not to:
>
> (c? (let self 42
> (+ self 10)))
Oh, sh*t ... that's where all those cells problems in my app come from.
And I have been wondering all those years what I have been doing wrong :->
Anyway, capture sounds like a good idea in this context, since it will
be a lot easier to debug than any of the (far more elegant) solutions
built around symbol-macros etc.
Thanks,
Peter
Peter Hildebrandt wrote:
> Ken Tilton wrote:
>
>> <cough> Sorry, I missed this originally since it was not about Arc,
>> but there might be another way to go. It depends on the real code,
>> which this NG should have beaten out of you, NDA or no. Not sure it is
>> better, but in one word: variable capture.
>
>
> Thought about it before. I really should have been more exact.
>
> So, there is one macro, and (Kenny is truly psychic) it is used to call
> a CFFI wrapped function and do some type juggling. Just use (call-fn fn
> var1 type1 var2 type2 result-type) and get the result back, properly typed.
>
> Since there might be some preparation involved (like, allocating foreign
> memory, type and other conversions), this expands in something like
>
> (with-return (result-type)
> (with-var (var1 type1)
> (with-var (var2 type2)
Uh-oh, nested usage? That would break my scheme, I think. But I would
have to see where you are trying to exchange hidden names to be sure.
Also, when you are feeling macro-empowered, we'll be wanting:
(with-vars ((var1 type1)(var2 type2)) ....
or in Arc:
(with-vars (var1 type1 var2 type2)...
> (fn (process var1) (process var2)))))
You could not hide the process-ing in the macro expansion? I do macros
as well as Cells, and would be happy to review what you are doing.
>
> So, yes, the order of the with's and the process's (outer and inner) is
> well defined since the code is auto generated.
>
>> (defmacro cll-outer (val &body body)
>> `(let ((outer-val ,val))
>> ,@body))
>>
>> (defmacro cll-inner (expr)
>> `(,expr outer-val))
>>
>> (export! cll-outer cll-inner) ;; my own little exporter
>> (cll-outer 10 (cll-inner 1+))
>> -> 11
>
>
> I thought of that, too, but then decided I did not like it and rather
> have outer make a gensym and have inner use that.
That is a fine alternative and more appropriate for this use case.
kenny
--
http://www.theoryyalgebra.com/
"In the morning, hear the Way;
in the evening, die content!"
-- Confucius
Kent M Pitman wrote:
> The correct general shape for a properly cooperative program of the
> kind I think you want to write is (I think [2]):
>
> (defmacro outer (val &body body)
> (macrolet ((inner (expr) `(,expr ',val)))
> ,@body))
>
> [2] I'm in a hurry so am just dashing this all off rather quickly, and
> didn't test any of it. Caveat emptor. At least it gives you something
> to think about. :)
Yuck yuck yuck, I disagree. I think the fact that you screwed up your
nested backquotes here is relevant -- if it hurts, don't do that (and
IMNSHO nested backquote hurts).
Anyway, for stashing values away in a lexical context so that another
macro can access them, we have symbol-macrolet and macroexapnd-1,
which I'm surprised no one mentioned.
(define-symbol-macro secret :default)
(defmacro outer (val &body)
(symbol-macrolet ((secret ',val))
,@body))
(defmacro inner (expr &environment env)
(let ((val (macroexpand-1 'secret env)))
`(,expr ',val)))
"Thomas F. Burdick" <········@gmail.com> writes:
Thomas,
I actually thought your suggestion a fun one, and worth having made, but
I hope you don't mind if I completely pick it (and your motivation) apart,
just to highlight a few other issues that rarely get much airing...
> Kent M Pitman wrote:
>
> > The correct general shape for a properly cooperative program of the
> > kind I think you want to write is (I think [2]):
>
> (defmacro outer (val &body body)
> (macrolet ((inner (expr) `(,expr ',val)))
> ,@body))
>
> [2] I'm in a hurry so am just dashing this all off rather quickly, and
> didn't test any of it. Caveat emptor. At least it gives you something
> to think about. :)
Later corrected as:
(defmacro outer (val &body body)
`(macrolet ((inner (expr) `(,expr ',',val)))
,@body))
> Yuck yuck yuck, I disagree. I think the fact that you screwed up your
> nested backquotes here is relevant -- if it hurts, don't do that (and
> IMNSHO nested backquote hurts).
There are probably cases involving other code where I'd allege that
getting something wrong on the first try mattered, so I don't discount
your technique as a general matter, but I don't think this is one of
the cases where I'd apply that critical technique. People do make typos.
In this case, I think I was simply in a hurry, and it was trivial to
notice this on a subsequent read when I wasn't hurrying. The outer
backquote's omission was simply a typo. The MACROLET must occur in
the end user's expansion or the the technique is useless. Once I'd
done that, I just didn't realize I was in two levels of backquote, so
the need to distinguish levels didn't occur, but that's one of the
simplest of the nested backquote idioms and should be easily navigable
by most experienced programmers.
If you are bothered by nested backquotes, you could remove them. In
fact, I started to do this and initially wrote:
(defmacro outer (val &body body)
`(macrolet ((inner (expr) (list expr ',val)))
,@body))
but was bothered that there weren't two levels of quotation. It looked
vaguely right but there was the nagging feeling I should be writing:
(defmacro outer (val &body body)
`(macrolet ((inner (expr) (list expr (list 'quote ',val))))
,@body))
I had to actually construct a test:
(let ((foo 3)) (outer foo (inner list)))
to find out whether the expected value, (FOO), got returned. Of course,
the one with (list 'quote ',val) turned out to do it, and this was confirmed
by interactive testing. But two things to note:
(a) The problem of getting it right still happens with single
backquotes. [The MACROLET aspect of the solution is held
constant.]
(b) It's way harder for me to look at the solution without the
backquotes. Backquotes, even nested ones, are a MATERIAL
improvement. You have only to look to this example, which dates
to just after backquote's introduction into MACLISP, to get
the issue:
http://www.maclisp.info/pitmanual/funnies.html#evaluating_new_ideas
> Anyway, for stashing values away in a lexical context so that another
> macro can access them, we have symbol-macrolet and macroexapnd-1,
Yuck yuck yuck, I disagree. I think the fact that you screwed up your
spelling of macroexpand-1 here is relevant ...
Heh. Just kidding. But you see how picking on someone's typo isn't
useful.
> which I'm surprised no one mentioned.
>
> (define-symbol-macro secret :default)
Or, perhaps, err here. But yes.
> (defmacro outer (val &body)
> (symbol-macrolet ((secret ',val))
> ,@body))
There's a missing backquote here, further illustrating the ease of just
making a typo and having it be nothing sinister.
> (defmacro inner (expr &environment env)
> (let ((val (macroexpand-1 'secret env)))
> `(,expr ',val)))
If you were going to do this, you'd be better off not doing the MACROEXPAND-1,
since it's not doing anything that wouldn't be done anyway if you just
put your SECRET variable inline there.
There are times you need to use MACROEXPAND-1 on macros (and symbol macros)
but usually they are places where you'll do further processing of the result
within the macro before returning it.
Correcting all the typos, and illustrating the error option, here's what
you get:
(define-symbol-macro -outer-
(error "~S used while not inside OUTER." 'inner))
(defmacro outer (val &body forms)
`(symbol-macrolet ((-outer- ',val))
,@forms))
(defmacro inner (operator)
`(,operator -outer-))
(let ((foo 3)) (outer foo (inner list)))
=> (FOO)
The use of SYMBOL-MACROLET is cute, but will trouble some people because
variables are more likely to get bound than functions. There are what
I consider acceptable rules of style that make what you wrote 100% safe
in terms of hygiene, but some people will just be happier with that in
macro form.
And, just as an aside, it's just a trick here that you avoid the
double-quotation issue because SYMBOL-MACROLET happens to have an
implicit quote built in. But that's a hack and is not somewhat
non-general as a workaround. In other words, I still recommend people
learning backquote better.
In article <·························@news.sunsite.dk>,
Peter Hildebrandt <·················@gmail.com> wrote:
> Say, I have the macros outer and inner, and I write the following:
>
> (outer 10 (inner +1))
>
> Is it specified, in which order outer and inner are expanded?
Outer is expanded. It returns a new form.
Whatever 'outer' returns will then be evaluated
as usual. Rember, the expansion of outer might
or might not have the 'inner' form.
> I see
> that in sbcl outer is expanded before inner, and I can do the following:
>
> (let (store)
> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
> (defmacro inner (expr) `(,expr ,store)))
>
> That is, store is first setf'd by outer, then read by inner.
>
> Is this implementation specific, or can I rely on this?
>
> If I have missed the obvious in the hyperspec, I'd be grateful for the
> pointer.
>
> Thanks,
> Peter
Rainer,
thanks for the quick response.
> Outer is expanded. It returns a new form.
>
> Whatever 'outer' returns will then be evaluated
> as usual. Rember, the expansion of outer might
> or might not have the 'inner' form.
Okay, so that means, if I create a binding in outer during expansion
time (as in my example below), this binding will be available at the
expansion of inner, right?
>> (let (store)
>> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
>> (defmacro inner (expr) `(,expr ,store)))
>>
So if I specify that inner is only to be used inside some surrounding
"outer", inner can be implemented this way, and I am on the safe side?
>> That is, store is first setf'd by outer, then read by inner.
>>
>> Is this implementation specific, or can I rely on this?
>>
>> If I have missed the obvious in the hyperspec, I'd be grateful for the
>> pointer.
>>
>> Thanks,
>> Peter
Peter Hildebrandt <·················@gmail.com> writes:
> Rainer,
>
> thanks for the quick response.
>
>> Outer is expanded. It returns a new form.
>> Whatever 'outer' returns will then be evaluated
>> as usual. Rember, the expansion of outer might
>> or might not have the 'inner' form.
>
> Okay, so that means, if I create a binding in outer during expansion
> time (as in my example below), this binding will be available at the
> expansion of inner, right?
With the "unhygienic" status, yes.
>>> (let (store)
>>> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
>>> (defmacro inner (expr) `(,expr ,store)))
Beware that closure don't travel nicely thru fasl files. IIRC, it's
implementation dependent whether STORE is still shared between the
(macro-function 'outer) and (macro-function 'inner) closures after
LOAD.
> So if I specify that inner is only to be used inside some surrounding
> "outer", inner can be implemented this way, and I am on the safe side?
For this you should rather use macrolet:
(defmacro outer (&body body)
"
In body, you can use (INNER stuff) to do something.
"
`(let ((your-binding))
(macrolet ((inner (arg) `(progn (do-something-with your-binding) (and-your ,arg))))
,@body)))
(outer
(hi)
(inner lo)
(yo))
--
__Pascal Bourguignon__
Rainer Joswig <······@lisp.de> writes:
> In article <·························@news.sunsite.dk>,
> Peter Hildebrandt <·················@gmail.com> wrote:
>
>> Say, I have the macros outer and inner, and I write the following:
>>
>> (outer 10 (inner +1))
>>
>> Is it specified, in which order outer and inner are expanded?
>
>
> Outer is expanded. It returns a new form.
>
> Whatever 'outer' returns will then be evaluated
> as usual. Rember, the expansion of outer might
> or might not have the 'inner' form.
or might or might not have it too:
(defmacro outer (arg &body body)
`(progn
,@body
(macrolet ((inner (&whole w arg) `(print ',w)))
,@body)))
(macroexpand '(outer 10 (inner +1)))
--> (PROGN (INNER 1) (MACROLET ((INNER (&WHOLE W ARG)
(LIST 'PRINT (LIST 'QUOTE W)))) (INNER 1))) ;
^^^^^^^^ ^^^^^^^^^
| |
+-- let's say you've got inner here. |
but what is this? ---------+
;-)
--
__Pascal Bourguignon__
On Jan 31, 5:15 am, Peter Hildebrandt <·················@gmail.com>
wrote:
> Say, I have the macros outer and inner, and I write the following:
>
> (outer 10 (inner +1))
>
> Is it specified, in which order outer and inner are expanded?
Consider:
(defmacro base1 (&rest crap) 'blorg)
(defclass derived (base1 base2 base3) ())
Do you see what the problem would be if (base1 ...) got expanded
before (defclass ...)?
The DEFCLASS macro completely controls the meaning of (base1 base2
base3). This syntax isn't a macro call.
The only way that some constituent of a macro call is itself a macro
call is if that constituent survives into the replacement form in such
a way that further macroexpansion of the replacement form eventually
recognizes it as a macro call.
Needless to say, this doesn't happen with the list of superclasses in
a DEFCLASS.
> I see that in sbcl outer is expanded before inner, and I can do the following:
>
> (let (store)
> (defmacro outer (val &body body) (setf store val) `(progn ,@body))
> (defmacro inner (expr) `(,expr ,store)))
>
> That is, store is first setf'd by outer, then read by inner.
>
> Is this implementation specific, or can I rely on this?
The problem is that INNER and OUTER are completely independent macros.
You are assuming that one or more INNER's is always embedded in an
OUTER, and that OUTER's do not nest.
Wha tabout this:
(outer
;; store variable is set up by outside outer
(inner ...) ;; inner uses this store
(outer ...) ;; this outer clobbers store with new value
(inner ...) ;; this inner uses clobbered value
)
If you want an outer macro to prepare some storage location that is
used by macros that are embedded within that form, you have to do a
little more work.
Firstly, to make sure that it's totally transparent, you probably want
to dynamically allocate a location for each macro, and give it a
different name (gensym).
You probably want to use macrolets for the inner macros. That is to
say, the outer macro generates a macrolet form which provides local
macros that make the embedded inner macros work. This macrolet can
be customized such that the inner macros know the identity of the
secret variable instantiated just for them by the outer macro (for
that particular expansion only). In a different expansion of the outer
macro, a whole new set of inner macrolets is generated, which know
about a different secret storage location.
The secret storage location can just be the SYMBOL-VALUE slot of a
gensym.
Something like:
(defmacro outer (form &body body)
(let ((secret-sym (gensym)))
;; Stash form in secret place.
;; NOTE: form is not evaluated.
(setf (symbol-value secret-sym) form)
`(macrolet ((inner (expr)
`(,expr ,(symbol-value ,',secret-sym))))
,@body)))
So when OUTER is being expanded the above expander function allocates
a gensym. The argument form goes into the value slot of that symbol
via the SETF. In the macro expansion, the body is wrapped in a
macrolet which defines an INNER macro. This INNER macro is customized.
Each occurence of OUTER generates slightly different source code for
INNER, because the source code of inner contains the identity of the
secret symbol, thanks to the ,',SECRET-SYM splice. An INNER macro
can only be used within an enclosing OUTER. It is expanded using the
definition provided by the most enclosing OUTER, and so uses the
secret location set up by that innermost OUTER.
Peter Hildebrandt wrote:
>
> Say, I have the macros outer and inner, and I write the following:
>
> (outer 10 (inner +1))
>
> Is it specified, in which order outer and inner are expanded?
Yes, outer is always expanded first.
Pascal
--
1st European Lisp Symposium (ELS'08)
http://prog.vub.ac.be/~pcostanza/els08/
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/