For Kenny Tilton: Why do I need macros revisited.

Kenny Tilton ktilton at nyc.rr.com
Fri Aug 22 07:15:27 CEST 2003


Chris Reedy wrote:
> 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!)

<g> looks a lot like the code I was writing when I began a Python port 
of my Cells project. I'll be open-sourcing the Lisp version soon, you 
can do the Python port. :)

You are absolutely right. Metaclasses are killer. I am surprised 
Pythonistas afraid of macros let them into the language! I actually had 
a metaclass implementation of Cells until I decided to release the 
source. The MOP is not part of the standard, and it shows across 
implementations. Hell, MCL does not even expose a MOP.

> 
> </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.

You will be delighted to know that one of Lisp priesthood sees no need 
for macros since what they do can be done with lambdas. Not sure if that 
is so where one is generating multiple top-level forms from one clause. 
Me, I like hiding implementation details as mush as possible, and having 
just one place to go when changing how something works.

As for the lambda being clunkier, un-hunh, and if you decide to change 
the mechanism after a lot of them have been coded, perhaps passing a 
second argument, here comes the mega-edit. Worse, what happens when the 
toy hack reaches maturity and you get the final version of the macro:

this:
(c? (+ 10 (left self)))

expands to:
  (make-c-dependent
    :code '((+ 10 (left self)))
    :rule (lambda (c &aux (self (c-model c)))
            (+ 10 (left self))))

One funny thing is that even if one open codes that, the readers you all 
are so worried about still do not know what C-DEPENDENT is.

Now picture a make-instance with five of those in a row. The text widget 
font is a function of a user preference, all four dimensions are a 
function of the font size and string length, the color may be a function 
of sysntax judgment for highlighting, etc etc. Now you cannot see the 
semantics for all the redundant, essentially meaningless wiring.

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

Hiding the wiring, where a function will not do because the argument is 
your source code.

> 
> 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. 

No, that is not a big reason. It is cool that one could do that readily 
if one stuck to the basic sexpr notation, but my experience to date is 
that I do not need a wholly new language as long as I can drop into new 
syntax (c?...) periodically to get what I would have created in a wholly 
new embedded language.

> 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?

Lispniks have not seen code become unreadable because of macros. That is 
something you all (well, most) are imagining. You are almost positing it 
as given. And it is simply not so. We have pointed out again and again 
that macros are no harder to understand than functions or classes, but 
no one addresses that point. Some say "I want to see all the legal 
Pythion instructions". Except when it is in a function, I guess. 
Strangely, quite a few of you have also conceded macros can leverage a 
language.

Well, it seems like we have covered everything pretty thoroughly. Back 
to RoboCup!

-- 

  kenny tilton
  clinisys, inc
  http://www.tilton-technology.com/
  ---------------------------------------------------------------
"Career highlights? I had two. I got an intentional walk from
Sandy Koufax and I got out of a rundown against the Mets."
                                                  -- Bob Uecker





More information about the Python-list mailing list