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

[constants] from a reader macro

14 views
Skip to first unread message

Tunc Simsek

unread,
Feb 23, 2000, 3:00:00 AM2/23/00
to
Hi,

I'm having trouble with lisp constants.

Suppose I define a reader macro:

(defun foo (stream char)
`(make-array 10000 :element-type 'double-float))

(set-macro-character #\@ #'foo)

In this silly example each time lisp sees a @ it will return
a big array.

The question is whether I can make it return the same array
each time it sees it:

(dotimes (i 10000)
@)

should only create one array, i.e. in my opinion, a CONSTANT.

Thanks in advance,
Tunc


Samir Barjoud

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
Tunc Simsek <sim...@tudor.EECS.Berkeley.EDU> writes:

Your reader macro function is returning a form that makes an array.
Instead, make it return an array by removing the backquote.

--
Samir Barjoud
sa...@mindspring.com

Erik Naggum

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
* Tunc Simsek <sim...@tudor.EECS.Berkeley.EDU>

| Suppose I define a reader macro:
|
| (defun foo (stream char)
| `(make-array 10000 :element-type 'double-float))
|
| (set-macro-character #\@ #'foo)

I assume this is either a serious confusion or an instructive example
whose purpose I don't understand, but to which you can apply answers.

| In this silly example each time lisp sees a @ it will return a big array.

answering the confusion part: it is important to keep in mind exactly
which part of "lisp" sees it under which conditions, and what it returns,
not to forget to what it returns it. the Lisp reader will see the @ and
return the _list_ it received from foo. if supplied at the top-level,
this list is now a form the _evaluation_ of which returns an array, but
the Lisp reader is by now long gone, having completed its job. if read
as part of some source code, it will only be folded into the source and
later processed with that source code.

| The question is whether I can make it return the same array each time it
| sees it:
|
| (dotimes (i 10000)
| @)

as indicated above, this is exactly identical to

(dotims (i 10000)
(make-array 10000 :element-type 'double-float))

and what happens from then on is not related to the Lisp reader at all,
but to the normal behavior of the evaluator and compiler.

| should only create one array, i.e. in my opinion, a CONSTANT.

however, if you remove the backquote in your misguided reader macro, foo
will return a new array that will be treated as a constant by whatever
called the Lisp reader each time, but again, the Lisp reader is long gone
when this decision is made.

it seems by your use of the backquote that your core confusion is to
believe that "macro" in "reader macro" is the same kind of "macro" as in
"macro function". it isn't. reader macros is a very powerful mechanism
to change the syntax of the language, move certain operations into
read-time (essentially pre-compile-time), and to abbreviate common forms.
reader macros actually make up all the syntax of the language, such that
the reader macro for #\( builds lists.

I think the reader macro system is absolutely fantastic, but it takes a
lot of skill to use it productively, and a lot of serious concern to see
when not to use it, just as it takes a lot of intellectual effort to keep
syntax simple and clean in general, much more than people generally think
-- as witness C++ and Perl, but I'll avoid the digression.

#:Erik

Tunc Simsek

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
I see your points. The answer I was looking for was the removal of
the "misguided" backquote. Thanks.

Tunc

I have a few remarks though.

On 24 Feb 2000, Erik Naggum wrote:

> * Tunc Simsek <sim...@tudor.EECS.Berkeley.EDU>
> | Suppose I define a reader macro:
> |
> | (defun foo (stream char)
> | `(make-array 10000 :element-type 'double-float))
> |
> | (set-macro-character #\@ #'foo)
>
> I assume this is either a serious confusion or an instructive example
> whose purpose I don't understand, but to which you can apply answers.

The real example defines a reader for #\[ to construct matrices, and was
way too long to put here. That one returns something like `(make-matrix
which should now read (make-matrix ... (i.e. without the backquote.

I must confess that I don't understand this remark, they behave in the
same way, their ranges are lisp code.

> reader macros is a very powerful mechanism
> to change the syntax of the language, move certain operations into
> read-time (essentially pre-compile-time), and to abbreviate common forms.
> reader macros actually make up all the syntax of the language, such that
> the reader macro for #\( builds lists.
>
> I think the reader macro system is absolutely fantastic, but it takes a
> lot of skill to use it productively, and a lot of serious concern to see
> when not to use it, just as it takes a lot of intellectual effort to keep
> syntax simple and clean in general, much more than people generally think
> -- as witness C++ and Perl, but I'll avoid the digression.

I agree that unnecessary complication should be avoided. My intent is to
get a simple way of expressing constant matrices which I use a lot and
don't like to type in (make-matrix ... all the time. I actually know of
one problem with using #\[ as a macro character: allegro uses it as
a super paranthesi.

>
> #:Erik
>
>


Michael Kappert

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
Erik Naggum wrote:

> * Tunc Simsek <sim...@tudor.EECS.Berkeley.EDU>
> | Suppose I define a reader macro:
> |
> | (defun foo (stream char)
> | `(make-array 10000 :element-type 'double-float))
> |
> | (set-macro-character #\@ #'foo)

> | The question is whether I can make it return the same array each time it


> | sees it:
> |
> | (dotimes (i 10000)
> | @)

In this example, the reader sees @ only once.

> | should only create one array, i.e. in my opinion, a CONSTANT.

> [...] if you remove the backquote in your misguided reader macro, foo


> will return a new array that will be treated as a constant by whatever
> called the Lisp reader each time, but again, the Lisp reader is long gone
> when this decision is made.

If you want the reader to return the same array every time it sees a @,
shouldn't FOO be defined something like

(let ((my-array ()))
(defun foo (stream char)
(or my-array (setf my-array (make-array ... )))))

Or did i misunderstand the original poster? It seems, a reader macro isn't
the best way to do this, DEFCONSTANT may be more appropriate.


Michael


--
Michael Kappert
Fraunhofer IITB
Fraunhoferstr. 1 Phone: +49(0)721/6091-477
D-76131 Karlsruhe, Germany EMail: k...@iitb.fhg.de

Erik Naggum

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
* Erik Naggum

| it seems by your use of the backquote that your core confusion is to
| believe that "macro" in "reader macro" is the same kind of "macro" as in
| "macro function". it isn't.

* Tunc Simsek


| I must confess that I don't understand this remark, they behave in the
| same way, their ranges are lisp code.

no. that's the issue. the "range" of macros is Lisp code which will be
processed by the caller, usually the compiler on code it processes. the
"range" of reader macros is Lisp objects the Lisp reader was asked to
pick up from some textual input source, such as by the compiler when it
compiles files. viz, a macro to build a matrix would return the code to
be evaluated or compiled instead of the macro form, while a reader macro
to build a matrix would return the matrix as a constant object.

moreover, they do not behave in the same way. a macro function is called
with Lisp code it can transform at will, while a reader macro is expected
to build Lisp objects from parsing input. the "domain" of the former is
Lisp objects and code, while the "domain" of the latter is characters and
streams. this must be understood in depth before you can make use of
reader macros productively. most Lisp programmers don't know how the
Lisp reader and printer work to begin with, or how to add new objects to
the read-write-consistency paradigm. indeed, not understanding how Lisp
has solved this very difficult problem is why most designers of protocols
and syntaxes get them so incredibly wrong.

| My intent is to get a simple way of expressing constant matrices which I
| use a lot and don't like to type in (make-matrix ... all the time.

this is good. however, you should regard your reader macro as a
short-cut for #.(make-matrix ...), not for (make-matrix ...) if you want
to build the matrix in the reader macro (and nothing else makes sense).

| I actually know of one problem with using #\[ as a macro character:
| allegro uses it as a super paranthesi.

which "allegro" is that? Allegro CL does not violate the standard by
interpreting [ and ] as anything but ordinary symbol name constituents.

#:Erik

Erik Naggum

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
* Michael Kappert <k...@iitb.fhg.de>

| If you want the reader to return the same array every time it sees a @,
| shouldn't FOO be defined something like
|
| (let ((my-array ()))
| (defun foo (stream char)
| (or my-array (setf my-array (make-array ... )))))

presuming that it will be called at least once, you could initialize the
binding with the value, or use what I prefer these days, load-time-value.

#:Erik

Tunc Simsek

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to

On 24 Feb 2000, Erik Naggum wrote:

> * Erik Naggum


> | I actually know of one problem with using #\[ as a macro character:
> | allegro uses it as a super paranthesi.
>
> which "allegro" is that? Allegro CL does not violate the standard by
> interpreting [ and ] as anything but ordinary symbol name constituents.

Here is an excerpt from my Allegro CL 3.0.1 (win) on-line doc:
[

Terminating macro character
Allegro CL for Windows extension
Description: acts as an opening super parenthesis. See ] .


]

Terminating macro character
Allegro CL for Windows extension
Description: acts as a closing super parenthesis. It is equivalent to
reading any number of closing parentheses, closing back to the last [ , or
to the top level if no unclosed [ has been read.

>
> #:Erik
>
>

Tunc


Tim Bradshaw

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
* Tunc Simsek wrote:
> Here is an excerpt from my Allegro CL 3.0.1 (win) on-line doc:

Remember that that's a completely different implementation than the
current ACL for windows (5.0.1).

--tim

Erann Gat

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
In article
<Pine.SOL.4.10.100022...@tudor.EECS.Berkeley.EDU>, Tunc
Simsek <sim...@tudor.EECS.Berkeley.EDU> wrote:

> I agree that unnecessary complication should be avoided. My intent is to


> get a simple way of expressing constant matrices which I use a lot and
> don't like to type in (make-matrix ... all the time.

Why not just:

(defconstant @ (make-matrix ...))

(Editorial comment: you should probably choose a different name than @,
constant-matrix-1 perhaps.)

Erann Gat
g...@jpl.nasa.gov

Erann Gat

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
In article <mnema2y...@rainbow.studorg.tuwien.ac.at>,
chei...@ag.or.at wrote:

> What do you (and of course others) think about the following piece of
> code? Comments appreciated.

A little top-level comment describing what this code is intended to do
would be helpful.

CHANGE-CLASS is one of those things like EVAL that ought to raise a
red flag whenever you see it. In this case you can simplify things
a lot by doing:

(defun file-reader (stream subchar arg)
"This function is to be used in a SET-DISPATCH-MACRO-CHARACTER definition."
(declare (ignore subchar arg))
`(make-instance (or (assoc (pathname-type (path file))
*type-file-alist* :test #'string=)
'file)
:path ,(read stream t nil t)))

and get rid of maybe-change-class.

You can also make you file type check case-insensitive (if that's what
you want) by using string-equal instead of string=.

Erann Gat
g...@jpl.nasa.gov

> ======================================================================
> (defclass file ()
> ((path :accessor path :initarg :path :initform nil)))
>
> (defclass ipd-file (file) ())
> (defclass pbf-file (file) ())
> (defclass pif-file (file) ())
>
> (defparameter *type-file-alist*
> '(("ipd" . ipd-file)
> ("pbf" . pbf-file)
> ("pif" . pif-file)))
>
> #|
> (defmethod print-object ((f file) stream)
> (print-unreadable-object (f stream :type t :identity nil)
> (format stream "~S" (path f)))
> f)
> |#
>
> (defmethod print-object ((file file) stream)
> (format stream "#f\"~A\"" (translate-logical-pathname (path file))))
>
> (defmethod initialize-instance :after ((file file) &rest init-args)
> (declare (ignore init-args))
> ;; ensure (path file) is a pathname
> (with-slots (path) file
> (when (and path (not (pathnamep path)))
> (setf path (pathname path))))
> (maybe-change-class file))
>
> (defmethod maybe-change-class ((file file))
> "If the type of the path of FILE is known in *TYPE-FILE-ALIST*, change
its class accordingly."
> (flet ((associate ()
> (assoc (pathname-type (path file)) *type-file-alist* :test
#'string=)))
> (if (and (pathnamep (path file))
> (stringp (pathname-type (path file)))
> (associate))
> (change-class file (cdr (associate)))
> file)))
>
> (defun file-reader (stream subchar arg)
> "This function is to be used in a SET-DISPATCH-MACRO-CHARACTER definition."
> (declare (ignore subchar arg))
> `(make-instance 'file :path ,(read stream t nil t)))
>
> (set-dispatch-macro-character #\# #\f 'file-reader)
> ======================================================================

Raymond Toy

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
>>>>> "Erann" == Erann Gat <g...@jpl.nasa.gov> writes:

Erann> CHANGE-CLASS is one of those things like EVAL that ought to raise a
Erann> red flag whenever you see it. In this case you can simplify things

Why is that?

A while ago I had written an interference simulation system for
cellular. It had mobile phone users that were outdoors and indoors,
so the interference seen by these two types of users were modelled
differently. Although I didn't implement this part, the users were
allowed to move around. If the indoor user moved too far from its
home, it became an outdoor user. I was going to implement this via
change-class.

I could have done this in other ways too like having a slot indicating
if the user was indoor or outdoors, but changing the class seemed just
as effective.

Is this a bad use of change-class?

Ray

Robert Monfera

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to

Erann Gat wrote:

> CHANGE-CLASS is one of those things like EVAL that ought to raise a

> red flag whenever you see it.

I disagree, and am interested in the justification logic from you, or
stories when a solution turned out to be outright flawed because of
CHANGE-CLASS. Here's my current thinking:

CHANGE-CLASS is a first-class citizen of OOP and CLOS, related to object
identity, inheritance and polymorphism. (It also comes handy for
prototype-based systems.) CHANGE-CLASS recognizes the fact that objects
(real or contrived) may substantially and qualitatively change over
time, while preserving their identity.

A simple example is a business partner class: business partner
relationship (which you'd want to depict in an object) has many facets.
A business partner may assume the role of a customer, vendor, investor,
creditor etc., in addition to their roles in the initial engagement with
our firm. A business partner may become the subsidiary of another firm
through acquisition, become unworthy of credit, become a competitor,
with obvious accompanying behavioral changes.

All these types of common changes are ideally reflected with
CHANGE-CLASS and I don't know of a way of doing it more elegantly. The
need comes up in many (most?) non-trivial OO-modeling problems in the
business world.

Another example, which has also something to do with GUI (for those who
mistakenly think OO is primarily useful for GUI purposes) is the
functionality of TurboTax or similar "wizard"-like programs. If you
indicate at the beginning of the form that you're married and you want
to file a joint return, it will have effects several screens later
(e.g., in the form of asking your spouse's name and income). You may
fill your return completely, including these parts. You may then change
your mind about the joint return, rightfully expecting the program to
change screens accordingly, while retaining data still relevant for the
tax calculation (and maybe still remembering your spouse's data in the
background in case you revert).

> In this case you can simplify things

> a lot by doing:
>
> (defun file-reader (stream subchar arg)

[...]


> (declare (ignore subchar arg))
> `(make-instance (or (assoc (pathname-type (path file))
> *type-file-alist* :test #'string=)
> 'file)
> :path ,(read stream t nil t)))

Yes, this is much simpler and shorter. He could also use a generic
function to issue MAKE-INSTANCE, having an uspecialized method and
symbol-specialized methods for PIF, PBF and IPD the benefit is that it
would yield an easily extendable framework (which may or may not be
Clemens' goal).

The class of an object in an application may not only change because the
real-world object (if any) changed, but because we acquired new
knowledge about it.

For this reason, even with streams, the use of CHANGE-CLASS is
potentially a good idea, for example, the analysis of the first few
hundred bytes would determine the type (class) of the file, like whether
it is fixed-width or delimited, or if there is a header line. Even for
the low-level analysis it may be desirable to exploit the behavior of
the default class. In this case Clemens could still call a GF from
within INITIALIZE-INSTANCE to perform the "class upgrade", which would
do (symbol) dispatch instead of a manual implementation of dispatch with
an alist.

Incidentally CL-HTTP uses CHANGE-CLASS in a somewhat similar manner to
Clemens' example when exporting URLs, although I didn't analyze it to
the extent that I can judge its appropriateness.

I have to admit though that CHANGE-CLASS can be misused, such as most
other functions. In the present case, if that's all Clemens wanted,
your solution gives the denser code (not implying that CHANGE-CLASS is
inappropriate).

Best regards
Robert

Erik Naggum

unread,
Feb 24, 2000, 3:00:00 AM2/24/00
to
* Tunc Simsek <sim...@tudor.EECS.Berkeley.EDU>

| Here is an excerpt from my Allegro CL 3.0.1 (win) on-line doc:

geez, that's _ancient_.

#:Erik

Pierre R. Mai

unread,
Feb 25, 2000, 3:00:00 AM2/25/00
to
Clemens Heitzinger <chei...@rainbow.studorg.tuwien.ac.at> writes:

> One advantage of my setup is that known file types are automatically
> used, ie, I can say (make-instance 'file :path "foo.pif") and get a
> PIF-FILE object. (Well, if somebody thinks this is a bad idea, please
> say so.)

So. ;)

IMHO you shouldn't overload make-instance to do this automagic
change-class. When a user says (make-instance 'a), he should be able
to expect to get an object whose class is a, and not some other (sub-)
class, regardless of the specific a in question.

I wouldn't object to something like

(make-file :path "foo.pif")

or even

(make-file-instance 'file :path "foo.pif")

or whatever. But overloading make-instance to do this is not very
clear, IMHO.

> This fits my situation. I suppose changing the class of an object in
> INITIALIZE-INSTANCE is ok.

I'd disagree, see above.

Note that I'm not objecting to change-class, just to doing the
change-class in initialize-instance.

Regs, Pierre.

--
Pierre Mai <pm...@acm.org> PGP and GPG keys at your nearest Keyserver
"One smaller motivation which, in part, stems from altruism is Microsoft-
bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]

Tim Bradshaw

unread,
Feb 25, 2000, 3:00:00 AM2/25/00
to
* Clemens Heitzinger wrote:
> One advantage of my setup is that known file types are automatically
> used, ie, I can say (make-instance 'file :path "foo.pif") and get a
> PIF-FILE object. (Well, if somebody thinks this is a bad idea, please
> say so.)

Although I'm not against CHANGE-CLASS (apart from the fact that on
some (non-current, as far as I know) implementations it's *extremely*
inefficient as it basically seems to cause all the effective method
caches to get flushed!), I think this is a really bad example.

If I say (make-instance 'x), I expect to get precisely an X, not some
other class.

I think that it's quite reasonable to have a `make appropriate class'
type function, but you'd want to avoid using MAKE-INSTANCE to do that.
I kind of think that you'd also want to avoid doing a CHANGE-CLASS in
this case, but that's really an implementation issue.

In some code I recently wrote I have things called UPTs
(urlpath-trees) which have several internal representations with
various tradeoffs, and the make-a-new-empty-tree function lets you say

(make-upt :type x)

where X is some kind of hint as to the representation you want (I
think you can say :LISTY, :HASHY, or :SMART). (:TYPE is an
unfortunate keyword name in this context, I know).

The :SMART ones actually use CHANGE-CLASS from listy to hashy if the
directories get big enough, which is a really nice use of CHANGE-CLASS
I think as it optimises lookup performance dynamically while keeping
the tree overhead small (small directories stay listy), and requiring
only one simple method on UPDATE-INSTANCE-FOR-REDEFINED-CLASS to do
all the work (and an :AROUND method on the add-child GF to trigger it
actually).

I measured it on some biggish trees and the change-class one is just
as efficient to build the tree, and more efficient for general lookup.
As far as I can see the alternative implementation would require some
kind of proxy class for the smart nodes which forwarded all methods to
the real object, and was willing to build a new real object from the
old one if need be. I had one that did that (because I felt it would
be more conventional for students to see) but it was just masses of
extra code, which you had to fix for no very good reason every time
the protocol for the non-proxy things changed. I guess there's a
design pattern for things like that, it sounds sufficiently like a
losing solution that there would need to be (`yes, it's OK to do this
dreadful crap, because there's a *pattern*'!)

(I'd include the code here but it's for a student exercise so I can't
quite yet...)

--tim


Robert Monfera

unread,
Feb 25, 2000, 3:00:00 AM2/25/00
to

I think the idiom makes sense, but I'm divided over the details.

On one hand, Pierre and Tim have a valid point: if MAKE-INSTANCE is to
return an instance of the specified class (and doing anything else is a
violation of the letter, but probably not the intent of ANSI CL spec),
then readers of the code may be puzzled. I don't know about safety
measures in Dylan, but CHANGE-CLASS in CL may change the class of the
newborn object to anything. (Technically speaking, implementations
should have no problems with doing whatever the user wants in an :AFTER
method of INITIALIZE-INSTANCE).

On the other hand, I don't like the idea of necessarily creating helper
functions like CREATE-FILE et al if this consideration is the _only_
reason to do so, as they unnecessarily increase the number of exported
functions. I also don't expect that any benefit arises from strictly
observing the letter of the spec, by other words, no reasonable
implementation or user code should depend on the class of the new
instance.

To bridge the gap in an inexpensive way, one could invent one single
generic function to piggyback on MAKE-INSTANCE while avoiding its
overloading. How about MAKE?

Robert

> I thought this would be a similar idiom in LISP (given Dylan's LISP
> heritage) but from the replies in this thread I guess not.
>
> Chris.

Chris Double

unread,
Feb 26, 2000, 3:00:00 AM2/26/00
to
pm...@acm.org (Pierre R. Mai) writes:

> IMHO you shouldn't overload make-instance to do this automagic
> change-class. When a user says (make-instance 'a), he should be
> able to expect to get an object whose class is a, and not some other
> (sub-) class, regardless of the specific a in question.

Why is that?

In Dylan it seems quite common to use make(<some-class>) and have make
return either <some-class> or a subclass. In the DUIM windowing
framework for example, you do a make(<push-button>) but you actually
get an instance of a subclass like <win32-push-button>. make(...) in
Dylan is the equivalent of make-instance in LISP.

Pierre R. Mai

unread,
Feb 26, 2000, 3:00:00 AM2/26/00
to
Chris Double <ch...@double.co.nz> writes:

> pm...@acm.org (Pierre R. Mai) writes:
>
> > IMHO you shouldn't overload make-instance to do this automagic
> > change-class. When a user says (make-instance 'a), he should be
> > able to expect to get an object whose class is a, and not some other
> > (sub-) class, regardless of the specific a in question.
>
> Why is that?

Mostly because there is no precedent in ANSI CL (or most libraries and
systems I've seen) for this sort of behaviour, so it would run counter
to the expectations of an informed user, which would be confusing and
lead to serious maintenance hazards, IMHO.

Note that I'm not saying that the way ANSI CL handles this is the only
true and right way. Since Dylan is another language, with another
specification and different precedents, overloading make to
automagically return some subclass is something that informed users of
Dylan can expect.

0 new messages