-
Notifications
You must be signed in to change notification settings - Fork 2
An issue with the accepted definition of Union #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Why is it unsafe to assume there are only finitely many
Can you please expand a bit why it is necessary to know |
I think you misunderstood that part. He's just saying that the type of I do think we should lift this entire theory section into the PEP though. What do you think?
I wondered the same thing. Anyway, this should be discussed in #5 |
@mniip Do you have time to replace the section on "Theory" in |
I was talking about how a static type is a set of values.
Let's simplify to Even without this, it's simply impractical to test this many values.
There are a couple of fundamental type operations that become nontrivial in presence of intersection types. Given some function type
The computation of the result type requires external knowledge about which conjunctions of types are satisfiable (i.e. are a non-empty type). The more precisely we can answer that question the narrower our result types become. For example given
|
@mniip I recognize that this is a wall of text, so bear with me.
I apologize for the unclear formulation earlier. The way it appears to me is the following
In summary:
|
The following example checks in both class A:
def foo(self) -> int: return 0
class B(A):
def foo(self) -> str: return "cheat" # type: ignore[override]
def show(obj: A) -> int:
return obj.foo()
x: A = B() # no error
reveal_type(A().foo()) # int
reveal_type(B().foo()) # str
reveal_type(show(B())) # int, no error since B ≤ₙₒₘ A ⟹ B<:A https://mypy-play.net/?mypy=latest&python=3.11&gist=89aa3c3f034d0fb9e0be4e0f8b6aa4db |
Any example with a type ignore should not be used to make an argument if the type ignore is used to break the rules. From false assumptions, you can construct any desired result. |
@mikeshardmind That's entirely missing the point. The question is: when a rule is broken, at which point is/should the error raised? Of course, any example demonstrating a broken rule must include a broken rule. Python has multiple kinds of subtyping relationships: nominal (
I hope we can agree on the following:
The only thing we disagree on is that you keep insisting that the PEP must mandate that type-checkers must error on using On the other hand, your proposal of erroring on |
It actually isn't. We can't design for what happens if you suppress errors, that's the realm of type checkers. A & B should be |
Missing the point again. If someone defines a class What you are implicitly stating is that structural subtyping must take priority over nominal subtyping. However, this is contrary to how type-checkers like Just because someone could not define a structural subtype of |
I'm not though, the class C(A, B) is already breaking the rules by existing, and your type ignore in your example is hiding that. We cannot define a set of specifications of correctness under the assumption that people are breaking other rules and supressing the warnings for them, or we will be stuck in an endless set of more and more ridiculous exceptions. |
I completely agree, and that's why an error needs to be raised inside the class body of
Rule breaks will inevitably need to happen, Python is too dynamic to be completely statically typed atm. The point we are discussing is if a rule break happens, then at which point a rule break is messaged. We both agree that a nominal subclass We do not agree that The contraposition is that it doesn't matter whether |
I said that A& B for incompatible definitions of A and B must reduce to Never. This is different from the expression itself raising
And that bound is uninhabited, see the very first post in this thread for what that means, along with typing.assert_never showing that it actually isn't just an abstract definition, but something used by both the person calling, and the function itself. other existing typing features need to be able to detect a type being uninhabited, this means
|
From a structural point of view, yes, but there is no necessity to do this reduction since anyone who passes in anything other than Also, secondly, I am not sure we agree on what it means for
Notes:
|
@randolf-scholz I was quite clear with my meaning, and you seemed well aware of it in the other thread where you tried to use a type ignore to circumvent it. If it is impossible for a type to exist that satisfies all components of an intersection, the intersection must resolve to Never. This is a discussion better had in the other thread. |
This is nothing that this PEP needs or should define, but it is up to the type checker maintainers to decide this. We won't be able to define when and how to detect problems for type checkers.
But nothing more. |
The text in PEP 483 that describes how unions behave actually doesn't do a very good job as a definition of a union type.
This lists some facts that are indeed true if we were to interpret
Union[A, B]
as a static type that stands for a set of values that is a set-theoretic union of the sets thatA
andB
stand for. However this does not actually defineUnion[A, B]
to be that set.Many have interpreted the first line: "Types that are subtype of at least one of t1 etc. are subtypes of this." as a definition:
However this is now how set-theoretic unions work, it is not in general true that$C \subseteq A \cup B \iff (C \subseteq A) \lor (C \subseteq B)$
Counterexample:
What people probably are thinking of is the formula$c \in A \cup B \iff (c \in A \lor c \in B)$ , which is actually true. But we cannot use this as a definition because that involves checking potentially infinitely many $c$ . Instead we want a definition in terms of the $\subseteq$ relation.
<:
akaThis is a solved problem and I'll cut to the chase: the formulas that define set-theoretic unions and intersections are as follows:
$$C \subseteq A \cap B \iff (C \subseteq A \land C \subseteq B)$$
$$C \supseteq A \cup B \iff (C \supseteq A \land C \supseteq B)$$
The duality between intersection and union lies in reversing the subset relation, not in replacing an "and" with an "or". In fact it is a well understood idea that transitive relations play extremely poorly with "or", and extremely well with "and". Hence the above definitions both using "and".
What about$A \cap B \subseteq ...$ and $... \subseteq A \cup B$ ? We have these formulas, as corollaries of the definitions above:
$$(A \subseteq D \lor B \subseteq D) \implies A \cap B \subseteq D$$
$$(D \subseteq A \lor D \subseteq B) \implies D \subseteq A \cup B$$
Proof
In #1, here, @erictraut lists all 4 of these on equal footing, labelled "non-controversial". I want to emphasize that two of these involving "and" are actually definitions by universal property, and the two involving "or" are special cases/corollaries.
What does the PEP-483 paragraph actually define?
Most of the bullet list is actually describing a reduction procedure for type expressions involving class literals and unions. The proposed algorithm of checking
C | D <: A | B
with(C <: A or C <: B) and (D <: A or D <: B)
only works exactly because each ofA
,B
,C
,D
can only be a class, as the only other possible form - union - gets flattened first. However in presence of intersections this algorithm already fails:A & (B | C) <: (A & B) | (A & C)
vsA & (B | C) <: A & B or A & (B | C) <: A & C
I'll also cut to the chase here: you want to reduce the left hand side to a disjunctive normal form, and the right hand side to a conjunctive normal form. The
|
on the left and the&
on the right can then be taken apart. But the&
on the left and the|
on the right cannot. Problems of the kindt1 & ... <: s1 | ...
wheret1
, ...,s1
, ... are class literals -- are in general irreducible and will require knowledge of the inheritance tree and whether a multiple inheritance child class exists/can be constructed.The text was updated successfully, but these errors were encountered: