Re: [Python-Dev] PEP 246: lossless and stateless
At 11:50 PM 1/15/05 +0100, Just van Rossum wrote:
Phillip J. Eby wrote:
But it _does_ perform an implicit adaptation, via PyObject_GetIter.
First, that's not implicit. Second, it's not adaptation, either. PyObject_GetIter invokes the '__iter__' method of its target -- a method that is part of the *iterable* interface. It has to have something that's *already* iterable; it can't "adapt" a non-iterable into an iterable.
Further, if calling a method of an interface that you already have in order to get another object that you don't is adaptation, then what *isn't* adaptation? Is it adaptation when you call 'next()' on an iterator? Are you then "adapting" the iterator to its next yielded value?
That's one (contrived) way of looking at it. Another is that
y = iter(x)
adapts the iterable protocol to the iterator protocol. I don't (yet) see why a bit of state disqualifies this from being called adaptation.
Well, if you go by the GoF "Design Patterns" book, this is actually what's called an "Abstract Factory": "Abstract Factory: Provide an interface for creating ... related or dependent objects without specifying their concrete classes." So, 'iter()' is an abstract factory that creates an iterator without needing to specify the concrete class of iterator you want. This is a much closer fit for what's happening than the GoF description of "Adapter": "Adapter: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces." IMO, it's quite "contrived" to try and squeeze iteration into this concept, compared to simply saying that 'iter()' is an abstract factory that creates "related or dependent objects". While it has been pointed out that the GoF book is not handed down from heaven or anything, its terminology is certainly widely used to describe certain patterns of programming. If you read their full description of the adapter pattern, nothing in it is about automatically getting an adapter based on an interface. It's just about the idea of *using* an adapter that you already have, and it's strongly implied that you only use one adapter for a given source and destination that need adapting, not create lots of instances all over the place. So really, PEP 246 'adapt()' (like 'iter()') is more about the Abstract Factory pattern. It just happens in the case of PEP 246 that it's an Abstract Factory that *can* create adapters, but it's not restricted to handing out *just* adapters. It can also be used to create views, iterators, and whatever else you like. But that's precisely what makes it problematic for use as a type declaration mechanism, because you run the risk of it serving up entirely new objects that aren't just interface transformers. And of course, that's why I think that you should have to declare that you really want to use it for type declarations, if in fact it's allowed at all. Explicit use of 'adapt()', on the other hand, can safely create whatever objects you want. Oh, one other thing -- distinguishing between "adapters" and merely "related" objects allows you to distinguish whether you should adapt the object or what it wraps. A "related" object (like an iterator) is a separate object, so it's safe to adapt it to other things. An actual *adapter* is not a separate object, it's an extension of the object it wraps. So, it should not be re-adapted when adapting again; instead the underlying object should be adapted. So, while I support in principle all the use cases for "adaptation" (so-called) that have been discussed here, I think it's important to refine our terminology to distinguish between GoF "adapters" and "things you might want to create with an abstract factory", because they have different requirements and support different use cases. We have gotten a little bogged down by our comparisons of "good" and "bad" adapters; perhaps to move forward we should distinguish between "adapters" and "views", and say that an iterator is an example of a view: you may have more than one view on the same thing, and although a view depends on the thing it "views", it doesn't really "convert an interface"; it provides distinct functionality on a per-view basis. Currently, PEP 246 'adapt()' is used "in the field" to create both adapters and views, because 1) it's convenient, and 2) it can. :) However, for type declarations, I think it's important to distinguish between the two, to avoid implicit creation of additional views. A view needs to be managed within the scope that it applies to. By that, I mean for example that a 'for' loop creates an iterator view and then manages it within the scope of the loop. However, if you need the iterator to remain valid outside the 'for' loop, you may need to first call 'iter()' to get an explicit iterator you can hold on to. Similarly, if you have a file that you are reading things from by calling routines and passing in the file, you don't want to pass each of those routines a filename and have them implicitly open the file; they won't be reading from it sequentially then. So, again, you have to manage the view by opening a file or creating a StringIO or whatever. Granted that there are some scenarios where implicit view creation will do exactly the right thing, introducing it also opens the opportunity for it to go very badly. Today's PEP 246 implementations are as easy to use as 'iter()', so why not use them explicitly when you need a view?
participants (1)
-
Phillip J. Eby