Sundar Narasimhan posted to ll1-discuss, under the subject line "Re:
how expressive are they?", about a really compelling application of
macros in software he worked on.  He said:
> 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. 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. (It's like having a Perl facility to modify the semantics of
> what Perl does when you do "s = foo;" or "s =~ bar;" :)

So I did most of what he described in Python, without macros.

#!/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()


-- 
<[EMAIL PROTECTED]>       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.

Reply via email to