[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.  I spent
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.


import weakref

def test():
    person = define_class('firstname', 'lastname', 'shoesize',
                           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):
    def lookup(self, *args, **kwargs):
        if args:
            return self.by_primary_key[tuple(args)]
            rv = []
            for item in self.by_primary_key.values():
                for (field, value) in kwargs.items():
                    if getattr(item, field) != value: break
            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.