[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: how expressive are they?
Sundar Narasimhan wrote:
> The big [macro that doesn't easily become a higher-order function] in
> our system is really similar to the define-class that I think Dan
> Weinreb pointed out. We have a similar facility that defines classes
> based on database tables.. that expands into classes/methods that
> define object managers (consider that you may want to maintain a
> hash-table of objects in your database that is indexed by it's primary
> keys -- which are often n columns where n > 1 :), select/update/delete
> transactions, even a matcher function so you can say things like
> (lookup-objects object-manager :some-column value1 :some-other-column
> value2). That alone saves us oodles of code, but the system also
> modifies setf and uses clos/flavors facilities to introduce
> "write-restriction" clauses.. so that when a programmer creates an
> object of this flavor and does (setf (some-field o) value) the db code
> actually kicks in to enqueue transactions to the server etc.
> You can do that without macros, at least in some languages.
Kragen: Nice example -- it helped me understand more about Python. But
I don't see any SQL or OQL at all.. or how your "in-memory" objects
are kept in sync w/ "other" clients. What exactly happens when two
people run that piece of python code? How exactly does the second
person's insert fail because of a primary key clash and how is it
handled.
And the bits you omitted are not trivial.. most of the book-keeping in
matching relational w/ object models is in associations and keeping
such things in sync. It's about 70-80% of the code.. it's exactly the
sort of repetition that macros are intended to avoid.
I do agree w/ some of what you say .. i.e. "some" languages indeed.
an hour and a half this afternoon implementing all of the above
(except for communicating with a server, but I do log the
communications that would need to happen) in Python 2.1. (I would
have had more trouble before Python 2.x, because Python didn't have
weak references.) I include it at the end of this post.
Of course, a real implementation would contain more error-handling
facilities, separate its namespaces better, would actually enqueue
transactions to the server instead of keeping a physical log of them,
would contain facilities for more complex queries, actually support
transactions, and other things, but none of those things require or
benefit from macros, either.
> And it does things like associations etc. (should a parent be
> deleted when all the children of an n-ary relationship get deleted?)
> Pretty cool stuff.
Yep. I didn't do the associations etc. here because I didn't feel
like extending this to include definitions of relationships.
> (It's like having a Perl facility to modify the semantics of what
> Perl does when you do "s = foo;" or "s =~ bar;" :)
Perl has such a facility; it's called "tie".
I don't think macros essentially relate to the facilities you
described, although maybe accidents of implementation require you to
use macros to implement them. The (xml-macro) example seemed much
harder to handle without macros.
Maybe we have found a problem space we can solve with either macros or
reflection, but in completely different ways. I get nervous when I
see Python novices using reflection, just as some Lisp masters get
nervous when they see Lisp novices defining macros.
Maybe, but I doubt it. Your example of tie leads me to believe I
haven't gotten across the point at all. Sigh. Maybe some other day.
--
#!/usr/bin/python
import weakref
def test():
person = define_class('firstname', 'lastname', 'shoesize',
'engine_displacement_cc',
primary_key=('firstname', 'lastname'))
bob = person('Robert', 'Johnson', 13, 50)
angela = person('Angela', 'Freggen', 8, 1700)
assert bob.firstname == 'Robert'
assert bob == bob
assert bob != angela
person('Robbie', 'Johnson', 13, 50)
assert bob.primary_key() == ('Robert', 'Johnson')
assert bob == person.lookup('Robert', 'Johnson')
assert bob != person.lookup('Angela', 'Freggen')
assert [angela] == person.lookup(shoesize=8)
assert [bob, angela] == person.lookup()
angela.lastname = 'Hogg' # she got married
assert person.transaction_log() == [
('insert', ('Robert', 'Johnson', 13, 50)),
('insert', ('Angela', 'Freggen', 8, 1700)),
('insert', ('Robbie', 'Johnson', 13, 50)),
('delete', ('Robbie', 'Johnson', 13, 50)),
('update', ('Angela', 'Freggen', 8, 1700),
('Angela', 'Hogg', 8, 1700)),
], person.transaction_log()
class instance:
def __cmp__(self, other):
dircmp = cmp(dir(self), dir(other))
if dircmp != 0: return dircmp
return cmp(self.value_tuple(), other.value_tuple())
def primary_key(self):
return tuple([getattr(self, fieldname)
for fieldname in self.primary_key_fields])
def value_tuple(self):
return tuple([getattr(self, fieldname) for fieldname in self.fields])
def __setattr__(self, field, value):
before = self.value_tuple()
self.__dict__[field] = value
after = self.value_tuple()
self.klass.log(('update', before, after))
def __del__(self):
self.klass.log(('delete', self.value_tuple()))
class define_class:
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
self.by_primary_key = weakref.WeakValueDictionary()
self._transaction_log = []
def log(self, item):
self._transaction_log.append(item)
def lookup(self, *args, **kwargs):
if args:
return self.by_primary_key[tuple(args)]
else:
rv = []
for item in self.by_primary_key.values():
for (field, value) in kwargs.items():
if getattr(item, field) != value: break
else:
rv.append(item)
return rv
def __call__(self, *args):
rv = instance()
for (field, value) in zip(self.args, args):
rv.__dict__[field] = value
rv.__dict__['fields'] = self.args
rv.__dict__['primary_key_fields'] = self.kwargs['primary_key']
rv.__dict__['klass'] = self
assert not self.by_primary_key.has_key(rv.primary_key())
self.by_primary_key[rv.primary_key()] = rv
self.log(('insert', tuple(args)))
return rv
def transaction_log(self):
return self._transaction_log
if __name__ == "__main__": test()
--
<kragen@pobox.com> Kragen Sitaker <http://www.pobox.com/~kragen/>
Edsger Wybe Dijkstra died in August of 2002. The world has lost a great
man. See http://advogato.org/person/raph/diary.html?start=252 and
http://www.kode-fu.com/geek/2002_08_04_archive.shtml for details.