[PEAK] Persistence styles, MDA, AOP, PyProtocols, and PEAK
fumanchu at amor.org
Wed Jul 7 17:06:25 EDT 2004
Phillip J. Eby wrote:
> > > [when("isinstance(db,PickleFile)")]
> > > def save_to(ob,db):
> > > # code to write out arbitrary object as a pickle
> > >
> > > Doesn't this look a *lot* easier to you than writing DM classes?
> >hmmm... not for the PickleFile, at least. ;)
> Why not?
Because single dispatch is a special case of predicate dispatch. With
such a simple example, it boils down to just syntax shuffling. I'm still
at a point in my own framework where I _benefit_ from having One Way to
dispatch: subclassing + composition. Obviously you're getting tired of
> >I see the modularity being
> >a big issue; in what module or layer would the first code example be
> >placed? Invoice.py? XMLDocument.py? App/Model.py?
> Probably something like 'my_app.xml_support', or
> 'accounting.xml_support'. If it was the 'bulletins' example,
> I'd probably
> put any storage stuff in 'bulletins.storage', just where the
> DM's go now.
> The point is, you can pick a place appropriate to the application or
> library involved, and for simple things, it's quite simple.
> And, much more
> complex things will be possible.
Hmmm.. I guess I'm just not to the point yet where I'm dispatching on
more factors than 1) object class and/or 2) storage mechanism. I see the
potential of predicate dispatch in the presence of more factors; I just
don't have the use cases (in the cases where, for example, I've needed
to hand-tune SQL, I've just bypassed the O-R mapper at that point). I'd
be interested to see something more complex. But don't waste hours
satisfying my curiosity. ;)
> """[(invoice, invoice.customer) for invoice in Invoices
> if invoice.status == coerce(stat) and duedate > coerce(due)]"""
> IOW, the evaluation is within the current variable namespace,
> so local and
> global variables are both available for use in the
> expression. No string
> interpolation or separate "bind variables" required.
I take it you do that at declaration time? That is, if you're going to
pass the "when" string around, you need to wrap up locals/globals in a
closure somehow? I've ended up supporting both early and late binding
with separate mechanisms. Early is done with bytecode hacking; late is
done via 1) keyword arguments to the expression, or 2) standardized
functions like today(), which returns a datetime.date. App developers
can supply their own functions.
> Also, I'll be developing constant-expression optimization as
> part of the
> parser for predicate dispatching, so it won't be too hard to
> include it
> here. That is, the 'coerce(stat)' and 'coerce(due)' would
> not need to be
> calculated on each iteration of the query. Really, any part
> of the query
> that doesn't depend on a loop variable can be computed just
> once, during
> the initial processing of the query.
Right. I stole Raymond's code to early bind function locals in order to
"do the same thing" with lambdas that I think you're doing with strings.
> Anyway, these queries will actually be easier to use than
> SQL. I don't
> like using strings to represent code, but the alternative
> approach of using
> expression proxies the way SQLObject and some other
> frameworks do, doesn't
> really work all that well for expressing arbitrary queries.
> There's no way to specify arbitrary joins properly, for example.
What would be the string-based way to specify such? This is where my
framework really falls down, IMO.
> And you can't call
> standard Python functions on things, you have to parenthesize your
> comparisons (because the bitwise & and | arithmetic operators
> have higher precedence than comparison operators), and so on.
Heh. 1) I can. 2) I don't have to. :) But mine's CPython only, at the
moment. I suppose it could work for Jython, if I cared to dig into the
> But by using strings, I can access the full parse tree, and
> do things like
> call a generic function to ask how to represent a given
> Python function in
> the SQL dialect that the query is being rendered for...
> e.g. something like:
> [when("function is int and isinstance(backend,Oracle)")]
> def function_as_SQL(backend,function,args):
> return "FLOOR(%s)" % args
> Hurray! Yet another kind of registry that I've wanted PEAK
> to have for a
> while, but that I now won't have to design and code!
Again, subclassing can do the same in as many keystrokes for single
dispatch. But you're looking for more flexibility. Actually, it's often
fewer keystrokes, since you tend to collect all of the
"isinstance(backend,Oracle)" declarations into a single declaration (a
DM, in this case) with single dispatch. At runtime, both techniques
might produce a single node in the dispatch graph. But single dispatch
uses classic code techniques (if-then's, composition, delegation) to
fake multiple dispatch; you would need to provide additional, similar
tools to the string-based predicate logic to make complex graph
production easier on the typist. Otherwise, you're just trading
behavioral cut-and-paste (copying method bodies) for dispatch
cut-and-paste (copying rule statements).
> Even if we have convenience functions to simplify the
> typing (e.g. 'register_sql_function(int,Oracle,lambda args:
> "FLOOR(%s)" % args)'), the full extensibility will be
> there if and when it's needed.
Right. Then you could write:
def register_oracle_function(func, disp):
register_sql_function(func, Oracle, disp)
...or even <mischievous grin>:
def int(self, args):
"FLOOR(%s)" % args
for func in [int, ...]:
register_sql_function(func, Oracle, getattr(OracleAdapter,
Mischief aside, it might be a way to ease the transition from single
dispatch (building such tools into the framework); then, when an app
developer wants something more complex, the rewriting is already half
done. Just a thought.
fumanchu at amor.org
More information about the PEAK