Weaver/Yarn Pattern in Python

Christian Stork python-list at cstork.org
Thu Jan 22 00:03:44 CET 2004


Hi Paul,

On Wed, Jan 21, 2004 at 01:24:41AM -0800, Paul Prescod wrote:
> I've tried to understand your problem statement in detail but it is alot 
> to keep in the head in pure abstraction (i.e. I don't have your specific 
> problem and haven't worked on this complicated a tree walker in a while 
> if ever). Also, I think there may have been some typos that are making 
> it harder to understand. For instance, is visitB supposed to be 
> syntactically identical to visitA except it calls weaveB's instead of 
> weaveA's? 

I'm sorry that's a typo. :-(  Corrections follow below in context...

> Is this the boilerplate that offends you?

The boilerplate *and* the unnatural name mangling.  I guess only some
kind of generic function simulation could help me with that one. ;-)
(If anybody is interested I could try and come up with a minimal version
of my problem in Dylan using generic functions.)

> But maybe I can help anyhow. Here's some code from your post:
> 
> > class Weaver:
> >     "A special visitor which weaves yarns"
> >     def __init__(self, yarns):
> >         self.yarns = yarns
> >     def visitA(self, a):
> >         for y in self.yarns:
> >             y.weaveA_First(a)
> >         for i, k in enumerate(a.kids):
> >             for y in self.yarns:
> >                 y.weaveA_Before(a, i)
> >             k.accept(self)
> >             for y in self.yarns:
> >                 y.weaveA_After(a, i)
> >         for y in self.yarns:
> >             y.weaveA_First(a)
                         Last
> >     def visitB(self, b):
> >         for y in self.yarns:
> >             y.weaveA_First(b)
                       B
> >         for y in self.yarns:
> >             y.weaveA_First(b)
                       B
Above is what I meant.  Cut-n-paste typos :(

> I'm going to presume that visitB was supposed to be more like:
> 
> >     def visitB(self, b):
> >         for y in self.yarns:
> >             y.weaveB_First(b)
> >            ... identical to visitA except A's swapped for B's ...
> 
> If it isn't identical to visitA (except for the B) then I don't know 
> what the boilerplate is. 

Well, I didn't intend it to be identical.  I meant to leave out the
Before and After methods, but your assumption makes a lot of sense given
how I presented my problem.  :-)  

In actuality, things are a bit more complicated.  Nodes are of different
kinds.  These kinds are defined by a tree grammar.  (You can find more
about this in my papers at my home page (see sig).)  Anyway, as an
example one kind of node might have exactly 3 kids with names K1, K2,
K3, and it makes sense to refer to them by these names.  Therefore I
have method signatures like

class SomeYarn:
    ...
    def weaveN_Before_K2(self, k2): # here k2 is the actual kid, not an index
         ...

This is slightly more convenient and has the big advantage that this
code is still correct after changing the order of kids in the tree
(which happend several times).

Now, in the case of node classes B and C, they did not have any children
according to their node kind.  That's why I didn't list the appropriate
methods.  But I like the (relative) simplicity of your below proposal.
So maybe I cut back on some of the convenience features and use a scheme
like yours.

...
> If I'm understanding right, I think each yarn has a two-D matrix of methods:
> 
>           A        B          C        D  ....
> First
> Before
> After
> Last
> 
> And then you've got another dimension for the yarns (that varies at runtime)
> 
> There are many ways to represent these axes in Python:
> 
> self.yarns[0]["A"]["First"]
> self.yarns[0]["A", "First"]
> self.yarns[0]["A_First"]
> self.yarns[0]["A"].First()

Nice and clean solutions.  Definitely better than my mangling.  

> class Weaver:
>     "A special visitor which weaves yarns"
>     def __init__(self, yarns):
>          self.yarns = yarns
>     def visit(self, a):
>          for y in self.yarns:
>              y.threads[a.type].First(a)
>          for i, k in enumerate(a.kids):
>              for y in self.yarns:
>                  y.threads[a.type].Before(a, i)
>              k.accept(self)
>              for y in self.yarns:
>                  y.threads[a.type].After(a, i)
>          for y in self.yarns:
>              y.threads[a.type].Last(a)

By now I made one addition to my weaver's visit method:
Don't require that all yarn code snippets are present -- just fall back
on some default behavior.  In our case that means to do nothing.

This change allows to reduce the boilerplate of yarns to a degree where
I can look at a yarn's definition and immediatly see the non-boilerplate
code.  (Nothing big, but it helps.)

...
> class X_Yarn_A_thread:
>       self.type == "A"
>       def First(self, arg):
>               assert arg.type == self.type
>               return self.first(arg)
> 
>       def Before(self, arg):
>               assert arg.type == self.type
>               return self.before(arg)
...

Makes sense.  Adds a little to the boilerplate tho.

> Hacking together method names with strings is only ever a sort of 
> syntactic sugar for some other design that uses dictionaries "properly". 
>
> Remember that Python has at its core a similar feature set to Scheme and 
> Lisp which are famously "flexible" and "powerful" and yet do not do any 
> name manging weirdness. It's great that Python allows that name manging 
> stuff but if you feel you HAVE to use it then you probably aren't 
> thinking in terms of higher order functions or objects in contrainers as 
> you could/should. You should think about hwo to do things without 
> method-name hacking before deciding that that's a better solution for 
> some reason of expedience or usability.

True.

Thanks a lot for help and discussion,
Chris

-- 
Chris Stork   <>  Support eff.org!  <>   http://www.ics.uci.edu/~cstork/
OpenPGP fingerprint:  B08B 602C C806 C492 D069  021E 41F3 8C8D 50F9 CA2F




More information about the Python-list mailing list