[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