[Python-3000] Draft pre-PEP: function annotations
Phillip J. Eby
pje at telecommunity.com
Sat Aug 12 18:39:15 CEST 2006
At 12:33 AM 8/12/2006 -0400, Collin Winter wrote:
>>I don't see the point of this. A decorator should be responsible for
>>manipulating the signature of its return value. Meanwhile, the semantics
>>for combining annotations should be defined by an overloaded function like
>>"combineAnnotations(a1,a2)" that returns a new annotation. There is no
>>need to have a special chaining decorator.
>>
>>May I suggest that you try using Guido's Py3K overloaded function
>>prototype? I expect you'll find that if you play around with it a bit, it
>>will considerably simplify your view of what's required to do this. It
>>truly isn't necessary to predefine what an annotation is, or even any
>>structural constraints on how they will be combined, since the user is able
>>to define for any given type how such things will be handled.
>
>I've looked at Guido's overloaded function prototype, and while I
>think I'm in the direction of understanding, I'm not quite there 100%.
>
>Could you illustrate (in code) what you've got in mind for how to
>apply overloaded functions to this problem space?
You just define an overloadable function for whatever operation you want to
perform on annotations. Then you define methods that implement the
operation for known types, and a default method that ignores unknown
types. Then you're done.
If somebody wants to do more than one thing with the annotations on their
functions, then everything "just works", since there is only one annotation
per argument (per the PEP), and each operation is ignoring types it doesn't
understand.
This leaves only one problem: the possibility of incompatible
interpretations for a given type of annotation -- and it is easily solved
by using some container or wrapper type, for which methods can be added to
the respective operations.
So, let's say I'm using two decorators that have a common (and
incompatible) interpretation for type "str". I need only create a type
that is unique to my program, and then define methods for the overloaded
functions those decorators expose.
QED: any incompatibility can be trivially solved by introducing a new
type. However, the most likely source of conflict is the need to specify
multiple, unrelated annotations for a given argument. So, it's likely that
most operations will want to interpret a list of annotations as just that:
a list of annotations.
But there is no *requirement* that they do so. Someone writing a library
of their own that has a special use for lists is under no obligation to
adhere to that pattern. Remember: any conflict can be trivially solved by
introducing a new type.
If you'd like me to sketch this out in code, fine, but you define the
specific example you'd like to see. To me, this all seems as obvious and
straightforward as 2+2=4 implying that 4-2=2. And it doesn't even have
anything specifically to do with overloaded functions!
If you replace overloaded functions with functions that expect to call
certain method names on the objects, *the exact same principles apply*. As
long as each operation gets a unique method name, any conflict can be
trivially solved by introducing a new type that implements both methods.
The key here is that introspection and explicit dispatching are bad. Code
like this:
def decorate(func):
...
if isinstance(annotation,str):
# do something with string
is wrong, wrong, *wrong*. It should simply be doing the equivalent of:
annotation.doWhatIWant()
Except in the overloaded function case, it's
'doWhatIWant(annotation)'. The latter spelling has the advantage that you
don't have to be able to modify the 'str' class to add a 'doWhatIWant()'
method.
Is this clearer now? This is known, by the way, as the "tell, don't ask"
pattern. In Python, we use the variant terms "duck typing" and "EAFP"
(easier to ask forgiveness than permission), but "tell, don't ask" refers
specifically to the idea that you should never dig around in an object's
guts to perform an operation, and instead always delegate the operation to it.
Of course, delegation is impossible in the case of a "third-party" object
being used -- i.e., one that can't be modified to add the necessary
method. Overloaded functions remove that restriction.
(This, by the way, is why I think Python should ultimately add an
overloading syntax -- so that we could ultimately replace things like 'def
__str__(self)' with something like 'defop str(self)'. But that's not
relevant to the immediate discussion.)
More information about the Python-3000
mailing list