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