Re: [Python-Dev] type categories
On Sat, Aug 24, 2002 at 05:37:36PM +0200, Alex Martelli wrote:
On Saturday 24 August 2002 05:15 pm, Jeremy Hylton wrote:
Good point, Oren. We now have two requirements for interfaces that are different than the standard inheritance mechanism. It should be possible to:
- inherit from a class without implementing that class's interfaces
- declare that a class implements an interface outside the class statement
It's harder to support the second requirement using the current inheritance mechanism.
The second requirement is a good part of what adaptation is meant to do.
I am not talking about situations where the object does not meet your expectations and needs to be adapted - I'm talking about situations where it actually does and the only problem is how to describe that fact properly. Adaptation is cool, but I don't see it as a replacement for anything that interfaces are supposed to achieve. Effective adaptation requires some kind of interface definition mechanism to work on top of. Oren
On Saturday 24 August 2002 07:06 pm, Oren Tirosh wrote:
On Sat, Aug 24, 2002 at 05:37:36PM +0200, Alex Martelli wrote:
On Saturday 24 August 2002 05:15 pm, Jeremy Hylton wrote:
Good point, Oren. We now have two requirements for interfaces that are different than the standard inheritance mechanism. It should be possible to:
- inherit from a class without implementing that class's interfaces
- declare that a class implements an interface outside the class statement
It's harder to support the second requirement using the current inheritance mechanism.
The second requirement is a good part of what adaptation is meant to do.
I am not talking about situations where the object does not meet your expectations and needs to be adapted - I'm talking about situations where it actually does and the only problem is how to describe that fact properly.
Adaptation IS one way to "describe that fact properly", given that checks are anyway constrained to happen at runtime. You just install an adapter from objects x of class X to protocol Y that receives x as an argument and whose body is just "return x" -- that's all. You may consider the adaptation mechanism too general to bend it to this purpose, but I look at it differently, namely: what's the gain that would justify a further, special-purpose mechanism that's usable only (e.g.) when all of X's methods already have the right name and order of parameters, but then we'd have to switch to another if there is renaming or reordering to be done? Unless some huge gain can be shown to come from having multiple mechanisms, I'd rather have just one -- "entities must not be multiplied without necessity". Should some caller, for some weird reason, need to distinguish whether object x was adapter to Y through an actual wrapper, or without the need for one, the caller can test "if x is adapt(x, Y):" -- I can't easily think of actual use cases, but, if there are any, they are covered anyway. Incldentally, I consider the best compile-time equivalent of adaptation I know to be Haskell's "instance" statement. Don't let the name mislead you -- Haskell is FP, not OO, and doesn't use "instance" to talk about what in Python we'd call instances of a type. Rather, Haskell uses statement "instance" to assert that a type T is an instance of a typeclass C. A typeclass is Haskell's equivalent of an interface (actually of a stateless abstract class, and then some, but that's another issue), and "type T instances typeclass C" is Haskell's way ot say "type T implements interface C". Renaming IS generally necessary. If you have an installation of the Haskell interpreter HUGS (comes with many Linux distros, for example -- can be downloaded from www.haskell.org also for Windows), have a look at demos/Lattice.hs -- you may find it readable even without knowing Haskell, since Haskell uses significant whitespace much like Python and has much notation in common with maths and other FP languages, Lattice.hs defines a typeclass "Lattice", and asserts that Bool instances Lattice (then goes on from there, of course, but let's stop to this part). But of course the key functions (would be methods for us, in an OO language) in Lattice are called meet and join (standard math terms, after all), while in Bool the corresponding functionality is given by functions named && and ||. No problem, of course: the instance statement is (MUST be!) able to "rename" -- to assert that, when using Bool as a Lattice, meet means && and join means || . Since instance is a compile-time thing, it doesn't need any 'wrapper' -- just some appendix to the compiler's symbol tables, of course. But if we want to remain OO, dynamic, and do name dispatching of methods, we WOULD need a wrapper of some kind to perform the same renaming. A facility that is SO special-purpose that it doesn't let me say "I have conceived this new interface Lattice, and existing class bool is an example of it" -- or forces me to distort Lattice's method names away from standards such as meet and join in order to fit them to the preexisting names of bool's methods/operators (and then how will I go about asserting that OTHER classes are also lattices...?), does not seem a good idea to me.
Adaptation is cool, but I don't see it as a replacement for anything that interfaces are supposed to achieve. Effective adaptation requires some kind of interface definition mechanism to work on top of.
The latter is a widespread opinion, but one from which I disagree. Using types as the "protocols" that adaptation works with is, IMHO, quite workable. And some of adaptation's aspects provide facilities, such as "third-party adapters" also working for renaming and similar issues, without which you could not achieve all "that interfaces are supposed to achieve" -- and I don't think those aspects should ALSO be duplicated by adding other mechanisms AS WELL AS adaptation. It seems to me Zope3 has it right in this respect (even though I think I disagree on other design choices -- I won't know for sure until I get a chance to try it out in production code), by making adaptation a key part of the interfaces' mechanisms. Alex
On Sat, Aug 24, 2002 at 11:33:38PM +0200, Alex Martelli wrote:
I am not talking about situations where the object does not meet your expectations and needs to be adapted - I'm talking about situations where it actually does and the only problem is how to describe that fact properly.
Adaptation IS one way to "describe that fact properly", given that checks are anyway constrained to happen at runtime. You just install an adapter from objects x of class X to protocol Y that receives x as an argument and whose body is just "return x" -- that's all.
I don't take it as given that "checks are anyway constrain to happen at runtime". I prefer a system that is future-proof enough to evolve into something that the compiler can use to do type inference. That is one of the reasons I don't want a typeclass / type category / interface / / type expression / whateveryouyouwannacallit to call any user-written Python code. (I don't want Python to become of those languages where user code can execute at compile time :-)
... what's the gain that would justify a further, special-purpose mechanism that's usable only (e.g.) when all of X's methods already have the right name and order of parameters, but then we'd have to switch to another if there is renaming or reordering to be done?
Being able to eventually perform many type checks earlier - at compile time or at module load time. Renaming and reordering really does have to be done at runtime in a dynamically typed language. Oren
On Sunday 25 August 2002 00:14, Oren Tirosh wrote:
On Sat, Aug 24, 2002 at 11:33:38PM +0200, Alex Martelli wrote:
I am not talking about situations where the object does not meet your expectations and needs to be adapted - I'm talking about situations where it actually does and the only problem is how to describe that fact properly.
Adaptation IS one way to "describe that fact properly", given that checks are anyway constrained to happen at runtime. You just install an adapter from objects x of class X to protocol Y that receives x as an argument and whose body is just "return x" -- that's all.
I don't take it as given that "checks are anyway constrain to happen at runtime". I prefer a system that is future-proof enough to evolve into something that the compiler can use to do type inference. That is one
A compiler able to do type inference had better be smart enough to recognize the special-case pattern: def noadapt(obj, proto): return obj install_adapter(noadapt, someclass, someprotocol) If that hypothetical compiler is unable to recognize this pattern (with whatever change of names except for a built-in install_adapter), its hypothetical type inference is FAR too puny for me to be happy to pay any substantial price for it. In particular, defining multiple mechanisms that partially overlap for the same tasks, for the sole purpose of making it hypothetically and marginally easier to draw the sole distinction of compile time versus runtime, IS a substantial price to pay in term of language complication. Conceptual distinctions between compile time and runtime are already "a price". One that may be worth paying, in general, for performance and in order to get error messages earlier. But, I think, one we should be quite wary to _extend_ -- particularly to extend to areas where we might well get away WITHOUT paying it.
time or at module load time. Renaming and reordering really does have to be done at runtime in a dynamically typed language.
Not necessarily, given _decent_ (hypothetical) type inference. The hypothetical decent type-inferring compiler would know about the install_adapter builtin. It could then hypothetically special-case method renaming by recognizing in the adapter pure-renaming patterns such as: def interfacemethod(self, *args): return self.obj.objmethod(*args) and generate code suitably when it recognizes that a given adapter does nothing but renaming. Blue-sky to some extent, but that sort of thing IS a good part of what type *inference* is about. Alex
This is straight from the library (xml.sax.saxutils) [chosen more because so at least it's real code than because nobody can argue that it is irrelevant. I find fascinating how much powerful is the argument "nobody does that often" in discussions about language design] def prepare_input_source(source, base = ""): """This function takes an InputSource and an optional base URL and returns a fully resolved InputSource object ready for reading.""" if type(source) in _StringTypes: source = xmlreader.InputSource(source) elif hasattr(source, "read"): f = source source = xmlreader.InputSource() source.setByteStream(f) if hasattr(f, "name"): source.setSystemId(f.name) ... the first problem is the "ontology" problem and how much we want to support someone who want to strictly check (a) "source has intentionally a file-like read" vs. just (b) "source has some read method"... This is indipendent of whether we have adapt or declarative interfaces or both. The above could be written as: source = adapt(source,xmlreader.InputSource) moving the code inside xmlreader.InputSource.__adapt__ . But does this address (a)? I would say no. Then one could simply not implement __adapt__ but leave the burden to the users to define my-type-with-a-good-read to xmlreader.InputSource adaptations. Or put code the code inside xmlreader.InputSource.__adapt__ and susbitute elif hasattr(source, "read"): with f = adapt(source,???) so the "ontology" problem is back. My point is not against adaptation, but that adaptation does not automagically solve all our problems without further thinking. I repeat, with both adaptation and interfaces, if one cares about contracts vs. just signatures, the ontology problem is with us. Adaptation is probably expressive enough. But the choice between (I) - mechanisms to ask and declare whether object implements protocol - mechanisms to register adapter factories between protocol A and B [I think this is Zope3 model] or (II) - just adaptation should be a choice also about convenience, readability, ... both ways the ontology problem is there. regards.
participants (3)
-
Alex Martelli
-
Oren Tirosh
-
Samuele Pedroni