[Python-ideas] Type Hinting Kick-off

Andrew Barnert abarnert at yahoo.com
Fri Dec 26 20:15:50 CET 2014

On Dec 26, 2014, at 18:59, Eugene Toder <eltoder at gmail.com> wrote:

> On Fri, Dec 26, 2014 at 10:26 AM, Andrew Barnert <abarnert at yahoo.com> wrote:
>> The builtin set (and therefore the undocumented MyPy/typing TypeAlias Set)
>> had a union method, but its signature is not that restrictive. It takes 1
>> or more arbitrary iterables of any element type, and of course it returns
>> a set whose element type is the union of the element types of self and
>> those. And the same is true in general for all of the builtin abstract and
>> concrete types.
> You are right. The union type solves the problem, and is a more precise type
> than with a lower bound. So we don't need lower bounds on type variables for
> collection methods, and maybe at all.
>> The _opposite_ problem--that it's hard to define the _actual_ type of
>> set.union or similarly highly parameterized types--may be more serious,
> Why is it hard? Isn't the actual type just:
> def union(self, *others: Iterable[Y]) -> Set[Union[X, Y]]
> where typing of vararg is similar to Java -- all elements must conform to the
> single type annotation.

I'm not sure you can just skip over that last point without addressing it. In this case, given iterables of element types Y1, Y2, ..., Yn, you can say that they're all type Iterable[Union[Y1, Y2, ..., Yn]]. I _think_ an inference engine can find that Union type pretty easily, and I _think_ that at least for collection methods there won't be any harder problems--but I wouldn't just assume either of those without looking carefully. And it certainly isn't true when we go past collection methods--clearly map and zip can't be handled this way.

> Also note that I posted set.union method as an example that needs a forward
> reference to a generic class. I was arguing that if we use strings for forward
> references, we'll eventually have complicated expressions in those strings,
> not just class names:
> class Set(Generic[X]):
>    # Note that the name "Set" is not yet available, so we have to use
>    # a forward reference. This puts the whole return type inside a string.
>    def union(self, *others: Iterable[Y]) -> "Set[Union[X, Y]]": ...
> Your type for set.union seems to prove the point even better than what I used.

Another way to write this, assuming that Set[X][Y] means Set[Y] or that there's some syntax to get from Set[X] to Set, would be to use a typeof(self) operator. Or a special magic __class_being_defined__ constant instead of an operator, or the normal type function with a slightly different meaning at compile time than runtime, or probably other ways to bikeshed it. The point is, at least this example only really needs the type of self, not an arbitrary forward declaration or an expression that has to be crammed into a string. Are there any good examples where that isn't true?

Also, should Set.union be contravariant in the generic type Set, or is it always going to return a Set[Something]? The two options there could both easily be handled by type expressions, or maybe with explicit forward declarations, but with implicit forward declaration via string?

I know Guido doesn't want to start allowing arbitrary expressions, but a compile-time typeof operator is a pretty simple special case; even pre-ISO C++ had that.

More information about the Python-ideas mailing list