Steven already answered many of these, so I’ll just snip the ones he didn’t.

On Sep 19, 2019, at 00:03, Philippe Prados <philippe.prados@gmail.com> wrote:


 * The static types in typing are not instances of type, so you need to work out what to do with them.
I do not understand the remark. My patch of 'mypy' accept this new syntax.

If you’re only adding __or__ to type, then List[int] | Tuple[int] is still an exception.

 * Making isinstance work isn’t a matter of accepting new syntax as you suggest, but making the values already created by the existing syntax work—and, since this appear to have been deliberated removed in 3.6, you need to explain why this was a mistake and should be undone. (Have you uncovered the reason for this change?) And whether it should affect just Union or other types.
Where I can find the argument to explain why this have been deliberated removed in 3.6 ?
In my implementation, they affect just Union. isinstance() can now accept type, Tuple or Union.

If isinstahce(2, List) is still a TypeError, then what happens with isinstance(2, List|Tuple)? Probably it should raise a TypeError complaining that List can’t be used in type checks, like isinstance(2, List) does?

 * What about except clauses? Shouldn’t they take unions if isinstance does? How does that work?
Good question. To accept `except TypeError | ZeroDivisionError:` in place of `except (TypeError, ZeroDivisionError):`, the impact is bigger, but why not ?

As Steven pointed out, the syntax will already handle this, but you do need to change the implementation of how exceptions check for a matching except spec while looking for a handler. I’m guessing it’ll be easy, but I haven’t looked at that code in a long time.

 * Should you be able to test whether List or List[int] is a subclass if List|Tuple or List[int]|Tuple[int]? If so, that reverses even more of the 3.6 change, and then you have to explain why you can’t use issubclass(List[int], Iterable[int]) or issubclass(List[Integral], List[int]) but can use this. If not, what’s the use case for issubclass with unions in the first place?
It's a question for "typing". My proposition change nothing about that.

No, typing avoids having to make this decision, because these aren’t types, and can’t be used in issubclass at all. If you’re changing that some these are now legal calls, you have to decide which ones, and what those calls return.

 * You will probably want to create a new builtin type for unions, rather than having a bunch of different parts of the Python core import from typing.
May be.


 * In addition to other benefits, someone (Stephen?) pointed out that builtin support could mean that, e.g., isinstance(3, int|str) could be just as efficient as isinstance(3, (int,str)), which alleviated multiple people’s concerns. Is that’s part of the proposal you should make that point; if not, explain why not.
The implementation use the tuple present in the Union type. The impact is just to check the type of the second parameter and replace it with the tuple from the Union.

If you have to do an instance check against a type with a subclass hook, and that you have to import first, on every call to find out whether the second parameter is a Union, that will slow down every isinstance call. And if you then have to get the tuple out of the instance dict, that could make unions significantly slower than tuples.

Checking a value against a built in type and accessing a member out of a C struct are a lot faster

It’s possible that neither of those will affect performance enough to matter, but you’d have to benchmark it to prove that.

 * Mentioning the wide variety of other languages’ typing systems that use | for related features would probably make it more compelling.
I find only Scala now.

The way you build sum types in ML and most languages derived or inspired by it (from Haskell to Swift) is with the | operator.

Most of those languages sum types give optional or mandatory constructor names to the alternatives, so it’s not quite the same thing as anonymous unions:

    IntStr = Int int | Str str

… this is a type whose values are an int or a str, but the way you construct one is with Int(2) or Str("a"), not with IntStr(2). And the way you extract the values is not by explicitly type-checking, but by pattern matching using | again, against the constructor names, something like this Int(n): Int(n*2) | Str(s): Str(f"{s} doubled")

For a more anonymous union, you usually use an explicit union or either or similar type: Either[int, str] gives you a type whose values are either an int or a str, and you access them with the Left and Right constructors from Either rather than specific named ones.

They could have syntactic sugar so that int|str means Either[int, str], it just doesn’t come up often enough—except the special case of Either[Error, X], which in some languages is the main way to do error handling. So if they do add any sugar, it’s usually for that special case, not the general one.

So you can see how this is all related to anonymous union types but not identical.

Scala is the exception here, but that’s because Scala has anonymous Union and Either as separate types, where Union means least upper bound on the lattice while Either is the direct one-or-the-other-and-nothing-else like Python’s Union. (And it’s the former rather than the latter that gets | for shorthand.(

The similarity is a lot like Python’s implicit Optional vs. the explicit Optional in other languages, where you construct an Optional[int] as a constructor call to Nothing() or Just(3) and then extract the value with pattern matching. Most of these languages have added a variety of kinds of syntactic sugar because it’s so common, so, e.g., something like x? means Optional[x], and ?x gives you a bool that says whether x is a non-empty value and !x means a pattern match on Just(x) and throw if it fails, and if let val = x: stuff is further shorthand for if ?x: val = !x; stuff and so on. None of this is identical to proposing ~int to mean Optional[int], but it’s all clearly similar, and shows how many languages have found it worth having shorthand like this.

So it’s the same with |. No language does something identical to what you’re proposing, but lots of languages do something similar and related.

One more thing:

In fact, having the two versions both ready could help clarify the discussion about PEP 585, and more generally about the advantages and disadvantages of going more toward a “two-kinded” type system vs. leaning more into “everything is first-class”.

I don’t think this was very clear, so apologies.

In lots of languages (Scala, Haskell, Swift, even C++) there’s syntax for operating on types, but it’s separate from the syntax for operating on values. PEP 585 is a step in that direction—the syntax of annotations is still the syntax of expressions, but the semantics are different (they just aren’t fully evaluated at all). That means you can put forward references in an annotation without getting a NameError. And it means you could define int|str as valid in annotations, without making it valid at runtime. This would avoid all the hard decisions and optimization issues, and it would be a lot closer to what Scala does, and it could even open the door for things like using the | operator for related but different meanings in type expressions and value expressions (e.g., the way ? means Optional in a type expression but optional-chaining in a value expression in Swift).

On the other hand, in Python, everything is a first-class value, including types, and everything that types do (constructing objects, type checks, even metaclass stuff) is determined by the normal language syntax and semantics operating on types as values. There are a lot of benefits to that. Which is probably why everyone who uses Python naturally expects that if int|str is a thing, it’ll be a thing you can pass around at runtime and use in isinstance and so on, even if that makes the language and the implementation a little more complicated and they don’t have a compelling use in mind for it.

I haven’t followed the discussion around PEP 585, but I suspect that when you take this PEP to the typing-sig people, these questions are going to be more relevant to them than the bikeshedding stuff about how to spell ~ and how other languages spell it. But I could be wrong.