On 4/22/21 5:00 PM, Paul Moore wrote:
On Thu, 22 Apr 2021 at 15:22, Adrian Freund <mail@freundtech.com> wrote:
On April 22, 2021 3:15:27 PM GMT+02:00, Paul Moore <p.f.moore@gmail.com> wrote:
but that's *absolutely* as far as I'd want to go. Note in particular that I don't want to constrain the return value The problem is that this isn't enough to have a type safe program. You need to also constrain the return type to make sure the returned value can be safely passed to other functions. But I don't want a type safe program. At least not in an absolute sense. All I want is for mypy to catch the occasional error I make where I pass the wrong parameter. For me, that's the "gradual" in "gradual typing" - it's not a lifestyle, just a convenience. You seem to be implying that it's "all or nothing".
I don't think that inferring the required return type breaks gradual typing, but it is required for people who want type safety. If I understand correctly your concerns with inferring return types for inferred protocols are that it might be to restrictive and prevent gradual typing. Here are some examples to show how gradual typing would still work. If you have any concrete examples where inferring the return type would break gradual typing let me know and I'll have a look at them. def foo(x: DuckType): # x has to have a .bar(self) method. # The return type of which is inferred as Any, as it isn't used x.bar() def bar(x): x.bar() def foo(x: DuckType): # x has to have a .read(self) method. # The return type of which ist inferred as Any, as the parameter to bar isn't typed. bar(x.read()) Contrast that with def bar(x: DuckType): # x has to have a .bar(self) method. # The return type of which is inferred as Any. x.bar() def foo(x: DuckType): # x has to have a .read(self) method that returns something with a .bar(self) method. # If we don't infer the return type our call to bar() might be unsafe despite both foo and bar being typed. bar(x.read())
I repeat, all I'm proposing is that
def f(x: int): ... def g(x: str): ...
def main(t: DuckTyped) -> None: if t[0] == 'version': f(t[1]) elif t[0] == 'name': g(t[1])
gets interpreted *exactly* the same as if I'd written
class TType(Protocol): def __getitem__(self, int): ...
def f(x: int): ... def g(x: str): ...
def main(t: TType) -> None: if t[0] == 'version': f(t[1]) elif t[0] == 'name': g(t[1])
How can you claim that the second example requires that " large parts of your codebase will either need explicit annotations or will be unchecked"? And if the second example doesn't require that, nor does the first because it's equivalent.
Both examples don't check the calls to f and g despite f and g both being typed functions and being called from typed functions. In a real codebase this will lead to a lot more instances of this happening. It would happen every time you do anything with something returned from a method on an inferred protocol
Honestly, this conversation is just reinforcing my suspicion that people invested in type annotations have a blind spot when it comes to dealing with people and use cases that don't need to go "all in" with typing :-(
I don't think this is an all in or nothing. You can infer return types of inferred protocols and still use gradual typing. It's just that not inferring return types causes problems for both full and gradual typing. Adrian Freund