Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Anaphoric macros and packages

123 views
Skip to first unread message

Gabor Melis

unread,
Dec 4, 2002, 6:02:34 AM12/4/02
to
In Paul Graham's On Lisp, there is a chapter dedicated to anaphoric
macros. One of the simplest ones is 'aif' that binds 'it' to the value
of the condition evaluated in an 'if':

(defmacro aif (test-form then-form &optional else-form)
`(let ((it ,test-form))
(if it ,then-form ,else-form)))

;;; example
(aif 5
(print it))

Now, it works fine if the macro definition and expansion are done in
the same package, but falls on its face when not:

;;; package 1 with the macro def
(cl:defpackage "P1"
(:use "COMMON-LISP")
(:export "AIF"))

(in-package "P1")

(defmacro aif (test-form then-form &optional else-form)
`(let ((it ,test-form))
(if it ,then-form ,else-form)))

;;; package 2 that uses the macro
(cl:defpackage "P2"
(:use "COMMON-LISP")
(:use "P1"))

(in-package "P2")

;;; example
(aif 5
(print it))

Loading this file in clisp (v2.27) results in:
*** - EVAL: variable IT has no value
and macroexpand-1 says that (aif 5 (print it)) is expanded to:
(LET ((P1::IT 5)) (IF P1::IT (PRINT IT) NIL))

P1::IT???
If I do a 'compile-defun-lisp' by hand in emacs for all toplevel
s-exps it works.

What did I do wrong?

Cheers, Gabor

Frode Vatvedt Fjeld

unread,
Dec 4, 2002, 6:18:41 AM12/4/02
to
me...@hotpop.com (Gabor Melis) writes:

> What did I do wrong?

Forget to export the symbol "it" from P1?

--
Frode Vatvedt Fjeld

Arthur Lemmens

unread,
Dec 4, 2002, 6:24:20 AM12/4/02
to

Gabor Melis wrote:
>
> In Paul Graham's On Lisp, there is a chapter dedicated to anaphoric
> macros. One of the simplest ones is 'aif' that binds 'it' to the value
> of the condition evaluated in an 'if':
>
> (defmacro aif (test-form then-form &optional else-form)
> `(let ((it ,test-form))
> (if it ,then-form ,else-form)))

> Now, it works fine if the macro definition and expansion are done in


> the same package, but falls on its face when not:

Yes. That's one reason why some people (me included) prefer something like
WHEN-LET:

(defmacro when-let ((var test-form) &body body)
`(let ((,var ,test-form))
(when ,var ,@body)))

;; Example:
(when-let (it (hairy-computation))
(print it))

> What did I do wrong?

Nothing, really. But I think that Paul Graham did something wrong with
the design of AIF.

Arthur Lemmens

Gabor Melis

unread,
Dec 4, 2002, 12:45:24 PM12/4/02
to
Arthur Lemmens <alem...@xs4all.nl> wrote in message news:<3DEDE5E4...@xs4all.nl>...

> Gabor Melis wrote:
> > What did I do wrong?
>
> Nothing, really. But I think that Paul Graham did something wrong with
> the design of AIF.

OK. I am feeling better, but I need to know why it doesn't work. It
looks right. And it works right if I do the compile-defun-lisp trick
in the original post.

Simon András

unread,
Dec 4, 2002, 2:40:31 PM12/4/02
to
me...@hotpop.com (Gabor Melis) writes:


> OK. I am feeling better, but I need to know why it doesn't work. It

Have you tried following Frode's advice and export the symbol IT
along with AIF from the P1 package? The double colon in the
macroexpansion is telling.

Andras

Erik Naggum

unread,
Dec 4, 2002, 6:37:10 PM12/4/02
to
* Gabor Melis

| What did I do wrong?

You used a poorly designed feature.

The key here is to get a short-hand for

(let ((expr (whatever)))
(when expr
(something-using-expr)))

but in the tradition of really badly designed languages, an implicit
binding is used instead of doing explicit bindings in the Common Lisp
tradition, which would also allow more than one binding/expression.

I would much prefer a syntax like this:

(whereas ((expr-1 (whatever-1))
(expr-2 (whatever-2)))
(something-using-expr-1-and/or-expr-2))

Reading it like the standard contract legalese, the key idea is that all
prior forms are true when an expression in the binding forms is evaluated
(just like the operator `and´) and prior variable are bound to the (true)
value of each prior expression. The body is an implicit `progn´ that can
thus rely on all of these forms having non-nil values.

Having exhausted all useful things to do, I, too, have been thinking about
how to do bindings and declarations more conveniently, and, not being the
least bit afraid of parentheses, have decided on binding forms that have
the general structure

(var-list [expression [decl-list]])

where var-list is a designator for a list of (symbols naming) variables,
expression may return multiple values bound to those variables with the
standard nil default, and decl-list is a designator for a list of types.
To be gratuitously super-clever, if the type of a variable in `whereas´
is an explicit union of `null´ and something else to signal that `nil´ is
a valid value, the result of the test is that using the succeeding value
(etc by induction) and if it is the last value, the entire form is used
for bindings, only. Some examples may yet have a slightly illuminating
effect:

(whereas (((value present-p) (gethash ...) ((or null ...))))
;; value is now actually obtained from the hashtable
...)
= (multiple-value-bind (value present-p) (gethash ...)
(declare (type (or null ...) value))
(when present-p
...)))

(whereas ((index (position ...) fixnum))
;; index now holds a fixnum index of an element actually in the sequence
...)
= (let ((index (position ...)))
(when index
(locally (declare (fixnum index))
...)))

(whereas ((cell (assoc ...) cons)
(value (cdr cell)))
;; cell now holds an actually matching cons cell from the alist
)
= (let ((cell (assoc ...)))
(when cell
(locally (declare (cons cell))
(let ((value (cdr cell)))
(when value
...)))))

My `let´ and other binding forms parallel this development with a final
(additional) argument that is the declared types, and allow a list of
variables for binding multiple values. Required arguments in lambda
lists may be typed just like methods on generic functions. The amount of
effort to get this done is surprisingly small, and making it fully
backward-compatible is almost easier than breaking things.

Please remember that the purpose of this stunt is to give people a good
reason to stay away from ill-designed and very un-Common-Lispy ways of
"improving" on the language. Macros that bind things and which neither
work when nested nor when naïvely exported in the package system may
appear "cool" to people who come from other languages, but not to the
seasoned users. Just like some may believe that "then" and "else" have a
place in `if´ "statements" because the language they /really/ want to use
does that, misguided attempts annoy people more than benefit a community.

--
Erik Naggum, Oslo, Norway

Act from reason, and failure makes you rethink and study harder.
Act from faith, and failure makes you blame someone and push harder.

JP Massar

unread,
Dec 5, 2002, 12:31:29 AM12/5/02
to
On 4 Dec 2002 03:02:34 -0800, me...@hotpop.com (Gabor Melis) wrote:

>;;; package 1 with the macro def
>(cl:defpackage "P1"
> (:use "COMMON-LISP")
> (:export "AIF"))
>
>(in-package "P1")
>
>(defmacro aif (test-form then-form &optional else-form)
> `(let ((it ,test-form))
> (if it ,then-form ,else-form)))
>
>;;; package 2 that uses the macro
>(cl:defpackage "P2"
> (:use "COMMON-LISP")
> (:use "P1"))
>
>(in-package "P2")
>
>;;; example
>(aif 5
> (print it))
>
>Loading this file in clisp (v2.27) results in:
>*** - EVAL: variable IT has no value
>and macroexpand-1 says that (aif 5 (print it)) is expanded to:
>(LET ((P1::IT 5)) (IF P1::IT (PRINT IT) NIL))
>
>P1::IT???

The fact that you question why it is P1::IT instead of
(what? P2::IT ?) says to me you need to understand about
how the Lisp Reader works, how INTERN works and how it
interacts with the Lisp Reader, and perhaps how macro expansion
works.

Briefly, the 'IT' is seen by the reader and turned into a
symbol in the P1 package by the reader (because you did an
'in-package' to P1). The defmacro 'really' looks like

(lisp::defmacro p1::aif (...)
`(lisp::et ((p1::it ,p1::test-form))
...
)

after it is read in.

The 'IT' in (aif 5 (print it)) after you do (in-package "P2")
is likewise really p2::it, and these two symbols have
absolutely nothing to do with one another.

Does this help?


>If I do a 'compile-defun-lisp' by hand in emacs for all toplevel
>s-exps it works.
>

Not sure why this is working for you. I don't think it should.

Kenny Tilton

unread,
Dec 5, 2002, 1:19:25 AM12/5/02
to

Slow down. It does /not/ look right.[1] Graham did not have different
packages. You added different packages. When tou change shit and shit
breaks, dwell first on the changes you made. Packages.

Did you really want to master packages and macros at /the same time/?
Brave soul! :)

If so, its simple: symbols "coded" by a macro, while reasonably viewed
to be expanded into the code where the macro is coded, do /not/ in fact
invade the namespace of a /different/ package of the code referencing
the macro, as would code actually written there.

So by default "it" does not invade the namespace of package P2. Even if
P2 "uses" P1. But! If P1 exports "it", the body of code provided to AIF
will "see" the "it" provided by p1.

[1] I have no idea why an emacs compile-defun-lisp
"trick" makes it work, i don't happen to do emacs. i do know truly
interpreted shit works where compiled doesn't (why I do not know) so
maybe you are in this area.

--

kenny tilton
clinisys, inc
---------------------------------------------------------------
""Well, I've wrestled with reality for thirty-five years, Doctor,
and I'm happy to state I finally won out over it.""
Elwood P. Dowd

Kenny Tilton

unread,
Dec 5, 2002, 1:22:44 AM12/5/02
to

Frode Vatvedt Fjeld wrote:
> me...@hotpop.com (Gabor Melis) writes:
>
>
>>What did I do wrong?
>
>
> Forget to export the symbol "it" from P1?
>

Jeez, could you be more terse? The guy's a newbie, maybe you could cut
it down to two words: "Export 'it'?" Or were you just being pedantic?

:)

Thomas F. Burdick

unread,
Dec 5, 2002, 2:16:21 AM12/5/02
to
Erik Naggum <er...@naggum.no> writes:

> I would much prefer a syntax like this:
>
> (whereas ((expr-1 (whatever-1))
> (expr-2 (whatever-2)))
> (something-using-expr-1-and/or-expr-2))

BTW, Erik, this is fantastic. I had was only somewhat satisfied with
my if-let macro (if-let (var expression) then [else]), and in fact, I
never use it. I do use my binding-cond macro sometimes
(binding-cond var (expr form...) ...). Yours is a cute name, but I
especially like the idea of having a full sequence of let forms
available.

> Having exhausted all useful things to do, I, too, have been thinking about
> how to do bindings and declarations more conveniently, and, not being the
> least bit afraid of parentheses, have decided on binding forms that have
> the general structure
>
> (var-list [expression [decl-list]])

Hey, great minds I guess (or bored or idle minds, maybe). I have been
using:

(vars-form [expression [&rest declarations]])
vars-form ::= var | var-list
var-list ::= (var-name ...)
var-name ::= var | (var type)

Which is a little more complicated to implement, but pretty much the
same idea. It makes type declarations easier than others, because I
use them far mor often than any other type of declaration. I think
this form is even less for those afraid of parens, though:

(tfb:let. ((((x integer) (y some-type)) (compute-values))
(((z number)) (compute-more)))
(foo x y z))

--
/|_ .-----------------------.
,' .\ / | No to Imperialist war |
,--' _,' | Wage class war! |
/ / `-----------------------'
( -. |
| ) |
(`-. '--.)
`. )----'

Frode Vatvedt Fjeld

unread,
Dec 5, 2002, 4:13:19 AM12/5/02
to
Kenny Tilton <kti...@nyc.rr.com> writes:

> Jeez, could you be more terse? The guy's a newbie, maybe you could
> cut it down to two words: "Export 'it'?" Or were you just being
> pedantic?

I'm just a believer in letting people making their own mistakes, I
guess. I think the aif thingy is a big mistake, and the OP's trouble
with packages was just derailing him from a speedy arrival at the same
conclusion..

--
Frode Vatvedt Fjeld

Rob Warnock

unread,
Dec 5, 2002, 6:47:37 AM12/5/02
to
Arthur Lemmens <alem...@xs4all.nl> wrote:
+---------------

| But I think that Paul Graham did something wrong with the design of AIF.
+---------------

Note that ANSI CL itself already contains some anaphora:

(let ((list '(nil (a b) nil (c d e) nil (f g h i) (j k l))))
(loop for i in list
when (third i) ; pun intended.
collect it))
=> (E H L)


-Rob

-----
Rob Warnock, PP-ASEL-IA <rp...@rpw3.org>
627 26th Avenue <URL:http://www.rpw3.org/>
San Mateo, CA 94403 (650)572-2607

Frode Vatvedt Fjeld

unread,
Dec 5, 2002, 6:53:21 AM12/5/02
to
rp...@rpw3.org (Rob Warnock) writes:

> Note that ANSI CL itself already contains some anaphora:
>
> (let ((list '(nil (a b) nil (c d e) nil (f g h i) (j k l))))
> (loop for i in list
> when (third i) ; pun intended.
> collect it))
> => (E H L)

I don't think the "it" here is much more magical than the other loop
keywords. If you try "collect (identity it)", it won't work.

--
Frode Vatvedt Fjeld

Gabor Melis

unread,
Dec 5, 2002, 9:37:05 AM12/5/02
to
mas...@alum.mit.edu (JP Massar) wrote in message news:<3deee20c....@netnews.attbi.com>...

> The fact that you question why it is P1::IT instead of
> (what? P2::IT ?) says to me you need to understand about
> how the Lisp Reader works, how INTERN works and how it
> interacts with the Lisp Reader, and perhaps how macro expansion
> works.
>
> Briefly, the 'IT' is seen by the reader and turned into a
> symbol in the P1 package by the reader (because you did an
> 'in-package' to P1). The defmacro 'really' looks like
>
> (lisp::defmacro p1::aif (...)
> `(lisp::et ((p1::it ,p1::test-form))
> ...
> )
>
> after it is read in.
>
> The 'IT' in (aif 5 (print it)) after you do (in-package "P2")
> is likewise really p2::it, and these two symbols have
> absolutely nothing to do with one another.
>
> Does this help?

Yes. And the other replies helped, too. Even though the spec does not
imply it, for some reason I had the mental picture of the result of
the macro expansion being fed to the reader. Now, it seems clear how
it (doesn't) work(s) and why exporting IT helped.

And as a side-effect (should be avoided, I know :-)), anaphoric macros
lost some of their allure.

Thanks, Gabor

Michael Hudson

unread,
Dec 5, 2002, 9:47:29 AM12/5/02
to
Frode Vatvedt Fjeld <fro...@cs.uit.no> writes:

> I don't think the "it" here is much more magical than the other loop
> keywords.

And, somewhat related to this thread, loop keywords are recognized by
symbol name and not identity. I wonder if the first implementation of
loop did that?

This is what makes it possible to write them as keywords (in another
sense of the word...), something that makes them stand out nicely
(with font-lock, anyway)..

Cheers,
M.

--
Richard Gabriel was wrong: worse is not better, lying is better.
Languages and systems succeed in the marketplace to the extent that
their proponents lie about what they can do.
-- Tim Bradshaw, comp.lang.lisp

Hannah Schroeter

unread,
Dec 5, 2002, 1:23:15 PM12/5/02
to
Hello!

Gabor Melis <me...@hotpop.com> wrote:
>[...]

>And as a side-effect (should be avoided, I know :-)), anaphoric macros
>lost some of their allure.

We're not so dogmatic wrt side effects on this side of the great
Scheme/CL divide. :-)

Kind regards,

Hannah.

0 new messages