Guido van Rossum wrote: >On 4/4/06, Tim Hochberg <[EMAIL PROTECTED]> wrote: > >(Could you not change the subject each time? Or if you do, could you >assume the reader hasn't necessarily read your previous posts? For >gmail users like me, each subject change starts a new thread -- like >in newsgroups -- and having so far ignored any thread that's grown to >100 messages or so I eagerly jump into any new thread; only to find >that it's hard to follow the first message there since it's assuming I >read the tail end of that other thread.) > > > Will do.
>(At least you included your Protocol class at the end, making your >post slightly self-contained.) > > > >>Considering generic function in combination adaption led to one more >>small change in the Protocol implementation that I'm playing with, and >>all of the sudden I'm left with something that I could actually use. >> >> > >Cool. Could you comment on the Protocol implementation I posted separately? > > I'll have a look at that ASAP. > > >>What I realized was that you could factor out the part that potentially >>has a lot of variation and suddenly you have a relatively simple >>framework that's extensible in all sorts of ways. By default, everything >>is the same as in the last iteration, but now it's much easier to change >>the behaviour by subtyping. >> >> > >Cool. We seem to be converging on a new kind of adaptation >implementation (I call it 2nd generation adaptation) where the >protocol is an object with register() and adapt() methods. In true >duck typing style all we have to do is agree on how to call adapt() >and register(), and whether there are other methods (like a >convenience to register using a decorator). You & I seem to have >chosen slightly different styles, but in essence we're very close. > >(Except that I can't see I understand the point of your TV example, >which seems very artificial.) > > > >>Let's return to the case that first bugged me about T->P dispatch. I >>have some tagged values and I want to assemble two of them to form a >>complex number. >> >>First we need a tagged value type. In reality I'd have this already >> >> >>> class TV(object): >>... def __init__(self, value, tag): >>... self.value = value >>... self.tag = tag >> >> > >I suppose this is implementing some theoretical abstraction popular in >some literature you've been reading recently? Us non-academicians >could use some help in gauging the significance of this class. > > Oh no.It's not the case that I'm guilty of reading any computer literature at all. It's a problem abstracted, probably poorly, from a real issue that I had and solved in a different manner. Essentially I read some large arrays from a file (touchstone format if anyone cares). These files have some minimal metadata including what format the complex numbers are represented in. You might think that I could convert them into complex numbers on the way in and be done with it. However, in my application I care about the "real" phase, not just the phase mod 360, so doing that to mag-angle or db-angle data destroys information that can't be recovered from the resulting real-imag data. Converting it all to mag-angle would work reasonably well except that it's painful to work with . What I actually do is convert it all to log(real+1j*imag). This preserves the phase and is relatively easy to work with in my domain, although it has some other drawbacks, notably for zero values. Had there been a nice framework in place to do adaption of this kind, I might have considered just keeping the data in its original format as long as possible and converting it only when needed. The real problem differs from the example I gave in that there's really only ever one object that contains two values plus the tag (real-imag, mag-angle or db-angle). In the example I was guilty of conflating two things I was interested in: converting a tagged value, and looking up adapters that operate on multiple values -- that probably just led to confusion. >>Then we need to subclass Protocol. I change the behaviour of keysof so >>that now things are looked up in the registry strictly based on their >>tags. >> >> > >I can't say I fully fathom the concept of "keysof". Perhaps you can >explain it better and then we can come up with a better name? > > I'm not fond of the name myself, but I couldn't think of anything better. >(Actually I think now I do understand it, and I still find the name >horrible. It represents all the keys that you want to look up in the >registry looking for an adapter. It gets passed the arguments to >__call__ -- which is really a shortcut to spell adapt in a cute way; >that confused me too -- and the base class assumes __call__ -- or >adapt -- is called with a single argument, like a typical adapt() >call. > Correct. >But your subclass calls it with tagged values. OK, so what you >*really* wanted there was keyword arguments?) > > I'm afraid I don't understand this. (Oh wait, I see you explain further down.) > > >>Note that this can take an arbitrary number of arguments. In the >>default implementation (see below) only a single argument is allowed, >>which gives you the basic adapter(obj) -> newobj behaviour. >> >> > >Which puzzled me at first (I had to read the Protocol class at the end >of your post before I could understand the rest). > > > >> >>> class TVProtocol(Protocol): >>... def keysof(self, *args): >>... try: >>... yield tuple(x.tag for x in args) >>... except AttributeError: >>... pass >> >>> ascomplex = TVProtocol('complex_from_tagged') >> >> > >Shouldn't that be > > ascomplex = TVProtocol('ascomplex') > >? > > Probably. The name is only used for repr and str, so it doesn't necessarily have to be coupled to the name the protocol is initially bound to. Still it was probably silly to include that example (these were all ripped out of a now ridicuously long and meandering docstring that I've been using both to test and to try out various things). >>Then I define some converters: >> >> >>> import cmath >> >>> @ascomplex.when(('real', 'imag')) >>... def complex_from_real_imag(real, imag): >>... return real.value + 1j*imag.value >> >>> @ascomplex.when(('mag', 'angle')) >>... def complex_from_mag_angle(mag, angle): >>... return mag.value * cmath.exp(1j * cmath.pi / 180 * angle.value) >> >>> @ascomplex.when(('db', 'angle')) >>... def complex_from_db_angle(db, angle): >>... return 10**(db.value/20.0) * cmath.exp(1j * cmath.pi / 180 * >>angle.value) >> >> > >Can we please get rid of the convention of naming the registration >function when()? I don't find it cute at all, and it's not >particularly indicative of what it does (registration). > > OK, no more when. I'm not entirely happy with register for a decorator name because it sounds like the argument (type/tag/whatever) is what's getting registered, not the following function. I'll try it out though and see how it looks and perhaps something better will come along. >>Here's some values that I can assume came from elsewhere: >> >> >>> tv_re, tv_im = TV(1, 'real'), TV(2, 'imag') >> >>> tv_db, tv_ang, tv_mag = TV(0, 'db'), TV(90, 'angle'), TV(2, 'mag') >> >> > >(Aside: I wonder if this would look less awkward if you changed >TV.__init__ so that you can write TV(real=1), TV(imag=2) etc.) > > Ah, here's where the keyword args come in. Yes that probably would be better. The example's artificial in any event. It's really all read from a file. > > >>And here's how I'd use it: >> >> >>> ascomplex(tv_re, tv_im) >>(1+2j) >> >>> ascomplex(tv_db, tv_ang) >>(6.1230317691118863e-017+1j) >> >>> ascomplex(tv_mag, tv_ang) >>(1.2246063538223773e-016+2j) >> >>> ascomplex(tv_db, tv_mag) >>Traceback (most recent call last): >> ... >>ValueError: adapter not found >> >> > >Or is anyone of these calling keysof() with multiple args? > > They all are. In each of the cases above, keysof is getting passed two objects and in turn returning a 2-tuple of tags. >>All of the sudden this is looking like something I could probably use. >> >> > >I'd like to hear what you think of my version and how you'd refactor it. > > I'll definately look that over. >>I also tried a simple generic function implementation on top of this (no >>inheritance, keysof just returned a tuple of types). That was also easy. >> >> > >Post this, please! > > class SimplePolyargProtocol(Protocol): """Build a simple multiple dispatch mechanism on top of the basic Protocol >>> addnum = SimplePolyargProtocol('addnum') >>> @addnum.when((int, int), (int, float), (float, int), (float, float)) ... def add(a, b): return repr(a + b) >>> @addnum.when((str, int), (str, str), (str, float), (int, str), (float, str)) ... def add(a, b): return '%s+%s' % (a, b) >>> addnum(5, 7) '12' >>> addnum(5, '33') '5+33' """ def keysof(self, *args): if not len(args): raise TypeError("%s expects at least one argument" (self, len(args))) yield tuple(type(x) for x in args) Yes, the example in the docstring is sortof bizzaro. No time to clean it up right now. Actually looking at this now, I suppose the len(args) check is not really needed. If someone wants to register a function to work on no arguments, who am I sto stop them? > > >>Could full blown generic dispatch be added just by subclassing and >>adding the correct, and obviously much more complex, version of keysof? >>It seems likely, but I'm not certain. If so, this is starting to look >>like a very promising approach. >> >> > >It would be useful to compare this to adaptation built on top of >generic functions. Alex seems to be convinced that adaptation is more >powerful than generic functions; but he hadn't considered the >possibility of having a generic function that's a factory (like the >iterator-factory in my example). I don't know if that affects his >opinion though; he seems to find it important that adaptation can >return an object that has multiple methods that belong together. > > > >>The updated Protocol implementation is below. >> >>class Protocol(object): >> all_protocols = set() >> >> > >What's the signigificance of all_protocols? You're not using it. > Right. It's vestigial from an earlier version that did 1st-generation adaption. In that it really did look like you wanted ways to search the protocols. When I started writing this version I still had that on the brain, so all_protocols got included. It got ripped out shortly after I posted my previous message. > The >key attraction of this class is that it *doesn't* need to keep track >of all protocols. In fact, IMO there's no use for concepts like "all >protocols that object X implements". Perhaps there could be a need for >this in a narrower concept (I can't rule out that Zope has some use >for this) but that could always be done by requiring all *relevant* >protocols subclass a certain base implementation that keeps track of >this. For general Python protocols I don't think it's necessary. For >that matter, after reading Alex's seminal post, I don't see any reason >why protocols should have anything to do with interfaces (although >it's fine for some framework's interfaces to be protocols). > > > >> def __init__(self, name, doc=''): >> self.name = name >> self.registry = {} >> self.__doc__ = doc >> self.all_protocols.add(self) >> def __repr__(self): >> return "<protocol %r>" % self.name >> __str__ = __repr__ >> def __call__(self, *args): >> for key in self.keysof(*args): >> adapter = self.registry.get(key, None) >> if adapter is not None: >> return adapter(*args) >> raise ValueError('adapter not found') >> >> > >So __call__ is what used to be call adapt. Or, rather, where Alex used >to write adapt(x, P) and where I write P.adapt(x), you just write P(). >Clever. > >Perhaps this could become the key to generic functions on top of >adaptation? That would be revolutionary! > > > >> def keysof(self, *args): >> if len(args) != 1: >> raise TypeError("%s expects 1-argument, got %s" (self, len(args))) >> obj = args[0] >> mro = type(obj).__mro__ >> for cls in mro: >> yield cls >> def register(self, adapter, *types): >> if not callable(adapter): >> raise TypeError("adapters must be callable") >> for t in types: >> self.registry[t] = adapter >> def when(self, *types): >> def decorator(adapter): >> self.register(adapter, *types) >> return adapter >> return decorator >> >> > >Sorry for the thoroughly random nature of this post. I kept going back >and forth and now I have to go -- but I still want to post it. > > Not a problem, regards, -tim _______________________________________________ Python-3000 mailing list Python-3000@python.org http://mail.python.org/mailman/listinfo/python-3000 Unsubscribe: http://mail.python.org/mailman/options/python-3000/archive%40mail-archive.com