For Kenny Tilton: Why do I need macros revisited.

Chris Reedy creedy at mitretek.org
Thu Aug 21 18:30:37 EDT 2003


For everyone -

   Apologies for the length of this message. If you don't want to look 
at the long example, you can skip to the end of the message.

   And for the Python gurus among you, if you can spare the time, I 
would appreciate any comments (including words like evil and disgusting, 
if you think they are applicable :-}) on the example here.

Kenny -

   I asked my question about macros with some malice aforethought. This 
is a topic I've thought about in the past.

> Andrew Dalke wrote:
> 
>> Kenny Tilton:
>>
>>> This macro:
>>>
>>> (defmacro c? (&body code)
>>>   `(let ((cache :unbound))
>>>      (lambda (self)
>>>        (declare (ignorable self))
>>>        (if (eq cache :unbound)
>>>   (setf cache (progn , at code))
>>> cache))))
>>
>>
>>
>> I have about no idea of what that means.  Could you explain
>> without using syntax?  My guess is that it caches function calls,
>> based only on the variable names.  Why is a macro needed
>> for that?

I sympathize with Andrew on this. I had to study this and the following 
code for awhile before I figured out what you are doing. However, I do 
have an advantage, I know lisp and the macro system and just how 
powerful the macro system is.

> (defun get-cell (self slotname) ;; this fn does not need duplicating
>   (let ((sv (slot-value self slotname)))
>     (typecase sv
>       (function (funcall sv self))
>       (otherwise sv))))
> 
> (defmethod right ((self box)) ;; this needs duplicating for each slot
>   (get-cell box right))
> 
> But I just hide it all (and much more) in:
> 
> (defmodel box ()
>   ((left :initarg :left :accessor left)
>    (right :initarg :right :accessor right)))
> 
> ....using another macro:
> 
> (defmacro defmodel (class superclasses (&rest slots))
>   `(progn
>      (defclass ,class ,superclasses
>        ,slots)
>      ,@(mapcar (lambda (slot)
>         (destructuring-bind
>             (slotname &key initarg accessor)
>             slot
>           (declare (ignore slotname initarg))
>           `(defmethod ,accessor ((self ,class))
>                        (get-cell self ',slotname))))
>           slots)))

Ok. The following is more lines of code than you have here. On the other 
hand, I think it does about the same thing:

<Example>

class cellvalue(object):
     def __init__(self):
         self._fn = None
     def _get_value(self, obj):
         if hasattr(self, '_value'):
             return self._value
         else:
             value = self._fn
             if callable(self._fn):
                 value = value(obj)
             self._value = value
             return value
     def _set_value(self, value):
         self._value = value
     def _del_value(self):
         del self._value
     def _set_fn(self, fn):
         self._fn = fn
         if hasattr(self, '_value'):
             self._del_value()

class modelproperty(object):
     def _init_cell(self, obj):
         obj.__dict__[self.name] = cellvalue()
     def __get__(self, obj, cls):
         if obj is None:
             return self
         else:
             return obj.__dict__[self.name]._get_value(obj)
     def __set__(self, obj, val):
         obj.__dict__[self.name]._set_value(val)
     def __delete__(self, obj):
         obj.__dict__[self.name]._del_value()
     def setfn(self, obj, fn):
         obj.__dict__[self.name]._set_fn(fn)
     def setname(self, name):
         self.name = name
         self.__doc__ = 'Model Property '+str(name)

class modeltype(type):
     def __init__(cls, name, bases, dict):
         super(modeltype, cls).__init__(name, bases, dict)
         modelprops = []
         for attr, decl in dict.items():
             if isinstance(decl, modelproperty):
                 decl.setname(attr)
                 modelprops.append(attr)
         if modelprops:
             __originit = getattr(cls, '__init__')
             def _modeltype_init(self, *args, **kw):
                 for attr in modelprops:
                     getattr(self.__class__, attr)._init_cell(self)
                 if __originit is not None:
                     __originit(self, *args, **kw)
             setattr(cls, '__init__', _modeltype_init)

 >>> class foo:
...     __metaclass__ = modeltype
...     x = modelproperty()
...     def __init__(self, x=None):
...         self.__class__.x.setfn(self, x)
 >>> z = foo(x=lambda self: self.a + 2)
 >>> z.a = 5
 >>> print z.x
7
 >>> z.x = -3
 >>> print z.x
-3
 >>> z.a = 15
 >>> print z.x
-3
 >>> del z.x
 >>> print z.x
17

I think that has most of the behavior you were looking for. As you can 
see from the example, I leaned (I'm not sure leaned is a strong enough 
work :-)) on the newer capabilities for metaclasses and descriptors. 
(And learned a lot about exactly how they work by writing this up!)

</Example>

Having looked at the two pieces of code, the only thing that struck me 
about how they're used is that the lambda expression needed in the 
Python version is clunkier than the version in the Lisp version. On the 
other hand, that might be addressed by a somewhat more elegant syntax in 
Python for constant code blocks, something that's been mentioned by 
others in recent messages.

Even though the Python setup (with two classes and a metaclass) is 
longer than the Lisp version, I'm not sure it's any clunkier or harder 
to understand.

So back to my original question, why do I want macros in Python?

Let me provide a candidate answer and rebuttal:

The real reason I want to do macros in Lisp is that they allow me to 
easily write new custom languages. Would a sufficiently powerful macro 
facility in Python allow me to do this? I suspect that the answer to 
this question would only be yes if that included some sort of parser 
generator facility.

Expanding on that: One of the nice things about Python (at least for 
those like me who like the language) is the clean syntax. However, that 
comes at a price: Needing a real parser to parse the language.

Lisp on the other hand has an extremely simple syntax that doesn't 
require a real parser. That allows me to create whole new "syntaxes" in 
lisp, since I'm not really changing the syntax, just changing how a 
given set of S-expressions are interpreted.

On the other hand, if I'm going to go to the complexity of including a 
parser generator so I can generate my own custom languages, it sounds to 
me like I'm about to reproduce what the Perl/Parrot people are up to. (I 
admit that I'd really like the time to look at what they're doing more 
deeply.)

Which brings me back to my original question: Would a macro facility in 
Python really buy me anything? And, in view of Alex's arguments, would 
that benefit outweigh the potential significant costs in other areas?

   Chris



-----= Posted via Newsfeeds.Com, Uncensored Usenet News =-----
http://www.newsfeeds.com - The #1 Newsgroup Service in the World!
-----==  Over 100,000 Newsgroups - 19 Different Servers! =-----




More information about the Python-list mailing list