Variance for protocols is awkward!
I've been playing with generic Protocols a lot lately, and I'm finding TypeVar variance to be really bad from a usability standpoint. PEP-544 required them to be exactly correct ( https://peps.python.org/pep-0544/#overriding-inferred-variance-of-protocol-c... ). In practice this means that if you add a method to a generic protocol class, you might have to rename the type parameters (because variance is essentially encoded in the name). And the same if you remove a method, or split a Protocol class into simpler sub-protocols. I had to have invariant, covariant, and contravariant versions of my `StateT` type parameter and I constantly had to think about which one to use. When I'm using structural subtyping, I *never* want to think about variance :) Could we change it? I found the discussion in the PEP, linked above, unconvincing. Perhaps I'm not understanding this section of the PEP. 1. It doesn't break transitivity of subtyping. The example has: ```python T = TypeVar('T') class P(Protocol[T]): # Protocol is declared as invariant. def meth(self) -> T: ... class C: def meth(self) -> float: ... class D(C): def meth(self) -> int: ... ``` And the PEP just asserts that D is not a structural subtype of P[float]. But I think it is! The PEP doesn't actually say "structural subtype", but subtyping of a protocol is always structural, so it's implied. The sentence from the PEP should be read as: "Now we have that D is a nominal subtype of C, and C is a structural subtype of P[float]. But D is not (?) a structural subtype of P[float] since D is a structural subtype of P[int], and P[int] is not a nominal subtype of P[float] because P is invariant." Isn't this reasoning wrong? I would say that programmers might just find it weird that P[int] is a structural subtype of P[float] but not a nominal subtype. But that's OK, there are lots of things that are structural subtypes of P[float] and not nominal subtypes. That's the whole point of structural subtyping. The PEP continues with: "There is a possibility to “cure” this by looking for protocol implementations in MROs but this will be too complex in a general case, and this “cure” requires abandoning simple idea of purely structural subtyping for protocols." I would say that the actual cure is to use (not abandon!) the simple idea of purely structural subtyping. If you want to know if D is a subtype of P[float], you would just check it directly (structurally). Nothing to do with MRO (well, you need to look in D's MRO to find the signatures of its methods, obviously). 2. Requires type inference. I'm not sure what this means. No type inference is necessary. You don't have to guess a supertype P[X] of D and check if it's a nominal subtype of P[float]. What might be weird is if you were trying to figure out how to instantiate a generic function taking P[T] and you wanted to find the type T that gives the least P[T] (wrt. subtyping). But here, I would just say that you don't want to do that wrt. nominal subtyping because P is a protocol. So it would be wrt. structural subtyping. 3. Prevents detailed error messages. Not sure what this means. Maybe there was a specific example? 4. Explicit is better than implicit. I would claim that what the PEP has is *implicit* in the sense that variance is determined by the uses of the type parameter. The only thing explicit is that I have to explicitly think about it. It's not explicit in the sense that I have any control over it. ==== Am I the only one that finds this awkward? Can we just relax the requirements and allow invariant type parameters that happen to be only used co- or contra-variantly? (It's still an error to use a covariant type parameter in a contravariant position and vice versa). -- [image: logo] Kevin Millikin Software Engineer kmillikin@deepmind.com If you received this email in error, please do not forward it to anyone else (it may contain confidential or privileged information), erase all copies and attachments, and let me know that it has gone to the wrong person.
Draft PEP 695 (https://peps.python.org/pep-0695/) proposes to eliminate the need to specify type variance for all generic classes — regardless of whether they're protocols. I'm hopeful that PEP 695 will be accepted, but if it's ultimately rejected, we could create a new smaller PEP that includes only the "inferred variance" portion. I think that's what you're asking for here. -- Eric Traut Contributor to Pyright & Pylance Microsoft
Yeah, that's what I want, at least for Protocols.
The Protocol PEP made a very specific choice to require correct variance
annotations and I'm skeptical that the rationale makes sense. In practice,
I'm finding it awkward and kind of pointless to have to worry about
variance for structural subtyping. It would be better for that PEP to have
said: don't worry about it, feel free to make everything invariant, because
it's not gonna matter.
That's already the case with subtyping instantiations of generic function
signatures, isn't it?
On Thu, Dec 8, 2022 at 5:09 PM Eric Traut
Draft PEP 695 (https://peps.python.org/pep-0695/) proposes to eliminate the need to specify type variance for all generic classes — regardless of whether they're protocols.
I'm hopeful that PEP 695 will be accepted, but if it's ultimately rejected, we could create a new smaller PEP that includes only the "inferred variance" portion. I think that's what you're asking for here.
-- Eric Traut Contributor to Pyright & Pylance Microsoft _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: kmillikin@google.com
participants (2)
-
Eric Traut
-
Kevin Millikin