macro FAQ

?

=?ISO-8859-1?Q?Hannu_Kankaanp=E4=E4?=

Jacek Generowicz said:
==== Example 1: a debugging aid ================================
=== Example 2: Alexander Schmolck's updating classes ===============

Here's another example of a real life situation
where I thought it'd be good to have macros in Python.
I was writing a GUI system and I had to define lots of
code twice, both for the x-axis and y-axis. Such
as


if y > self.y - bs and y < self.y + bs + self.height:
return (abs(x - self.x) < bs,
abs(x - (self.x + self.width)) < bs)
else:
return False, False


and then


if x > self.x - bs and x < self.x + bs + self.width:
return (abs(y - self.y) < bs,
abs(y - (self.y + self.height)) < bs)
else:
return False, False


Obviously this was quite unsatisfactory. I ended up
putting the axis code in a separate class so I could
use them interchangeably. I.e. If I passed
func(self.y, self.x)
and then
func(self.x, self.y)

I would get the same effect on both axises. But this
would've been an excellent place for macros IMO (unless
there's a more clever solution as a whole). Using macros
that combine both function call and "code block" syntax,
I could've written a simple function like this:

defBoth getSize(self):
return self.size

And it would've been expanded to

def getWidth(self):
return self.width

def getHeight(self):
return self.height

The macro would've had to change all "size" to either
"width" or "height", and also change "pos" to either "x"
or "y" and so on.

This way, I could've got no duplicated code but
also a more intuitive interface than I currently have
(to get width, one needs to type obj.x.getSize() instead
of obj.getWidth()). And it's obvious this kind of "defBoth"
wouldn't be added as a language level construct -- Thus
macros are the only good solution.
 
J

Jacek Generowicz

(e-mail address removed) (Hannu Kankaanpää) writes:

if y > self.y - bs and y < self.y + bs + self.height:
return (abs(x - self.x) < bs,
abs(x - (self.x + self.width)) < bs)
else:
return False, False


and then


if x > self.x - bs and x < self.x + bs + self.width:
return (abs(y - self.y) < bs,
abs(y - (self.y + self.height)) < bs)
else:
return False, False

Obviously this was quite unsatisfactory. I ended up
putting the axis code in a separate class

Do you mean "function" rather than "class" ?
so I could
use them interchangeably. I.e. If I passed
func(self.y, self.x)
and then
func(self.x, self.y)

I would get the same effect on both axises. But this
would've been an excellent place for macros IMO

I don't see what you gain by using a macro, wrt to using a function in
_this_ case. As no Python macro system exists, I cannot form an
opinion of what the two approaches would look like in Python, but in
Lisp the calls to the macro would look just like the calls to the
function, I think. The only difference would be in the definition of
the macro/function, and the macro would be no simpler, so you wouldn't
really gain anything.
Using macros
that combine both function call and "code block" syntax,
I could've written a simple function like this:

defBoth getSize(self):
return self.size

And it would've been expanded to

def getWidth(self):
return self.width

def getHeight(self):
return self.height

The macro would've had to change all "size" to either
"width" or "height", and also change "pos" to either "x"
or "y" and so on.

So you would not only replace whole symbols, but even fragments of
symbols (getSize -> getHeight), and thus macically/implicitly create
new symbols. Many people consider this bad practice. The objection
being that readers of the code come across a symbol in the source, go
off and search for its definition, and tricks like this mean that they
end up wasting their time.

The defstruct macro does this sort of thing; (defstruct foo ... )
would "magically" define a constructor called "make-foo" (and other
stuff). IIRC, the experience with defstruct led the designers of CLOS
to explicitly avoid doing this in CLOS' defclass macro (but I'm far
from the world's leading authority on Lisp history).

Incidentally, in this case, having a string based code representation
would make your job much easier than the structured representation
which Lisp uses. In a string you'd merely do a search and replace; in
an s-expression you would have to recursively search for all symbols
in all sub-expressions, and then do the search and replace within the
name of each symbol you find.
This way, I could've got no duplicated code but
also a more intuitive interface than I currently have
(to get width, one needs to type obj.x.getSize() instead
of obj.getWidth()). And it's obvious this kind of "defBoth"
wouldn't be added as a language level construct -- Thus
macros are the only good solution.

Cue metaclass solution ... :)

(I suspect that even with metaclasses, you wouldn't be able to avoid
eval ... and that amounts to "informally" writing a macro)
 
?

=?ISO-8859-1?Q?Hannu_Kankaanp=E4=E4?=

Jacek Generowicz said:
Do you mean "function" rather than "class" ?

Actually, I did mean class. Normally I'd have

class Widget:
def __init__(self):
self.x = 0
self.y = 0
self.width = 0
self.height = 0

def getWidth(self):
return self.width # normally they wouldn't be this empty!

def getHeight(self):
return self.height

But by wrapping it inside a new class, I could get rid of
the duplication (partly, at least):

class Widget:
def __init__(self):
self.x = Widget.Axis(0, 0)
self.y = Widget.Axis(0, 0)

class Axis:
def __init__(self, pos, size)
self.pos = pos
self.size = size

def getSize(self): # this is only once here now
return self.size

While this tiny example doesn't show decrease in code
size, it shows that I have a common definition for all
"Axis"-specific code inside the appropriate Axis class.
Rest of the Widget methods would be in the widget class.
Thus self.x.getSize() instead of self.getWidth().
I don't see what you gain by using a macro, wrt to using a function in
_this_ case.

Ok, it was a bad example. I hope the code above shows
a bit more clearly what I wanted. Anyway, without the code
in the axis-class, I would've had to often say

self.x = func(self.x, self.y, self.width, self.height)
self.y = func(self.y, self.x, self.height, self.width)

Instead of

func(self.x, self.y)
func(self.y, self.x)

Which could modify the axis-specific stuff within the
func()tion. (self.x is no longer a non-modifiable number,
but a modifiable class)
So you would not only replace whole symbols, but even fragments of
symbols (getSize -> getHeight), and thus macically/implicitly create
new symbols. Many people consider this bad practice.

Well, I don't, really. Like any macro that could do something
weird, it just needs to be properly understood by anyone who
wishes to read the code.
Incidentally, in this case, having a string based code representation
would make your job much easier than the structured representation
which Lisp uses. In a string you'd merely do a search and replace; in
an s-expression you would have to recursively search for all symbols
in all sub-expressions, and then do the search and replace within the
name of each symbol you find.

Well, such a recursive search isn't a problem - With help from
a macro ;)
Cue metaclass solution ... :)

How could metaclasses help? I'm quite inexperienced with them.
Anyway, if I take the eval route, I might as well do

defBoth('''getSize():
return size''')

,retreive appropriate locals() from the stack and modify it
to include the new functions. I'd rather not, though :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,097
Messages
2,570,622
Members
47,235
Latest member
LuisaHamle

Latest Threads

Top