C
Chris Reedy
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.
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.
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)
.... __metaclass__ = modeltype
.... x = modelproperty()
.... def __init__(self, x=None):
.... self.__class__.x.setfn(self, 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
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.
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)
.... __metaclass__ = modeltype
.... x = modelproperty()
.... def __init__(self, x=None):
.... self.__class__.x.setfn(self, 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