Skip to content
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

What if duck types are unions of all the ducklings #11516

Open
KotlinIsland opened this issue Nov 11, 2021 · 9 comments
Open

What if duck types are unions of all the ducklings #11516

KotlinIsland opened this issue Nov 11, 2021 · 9 comments

Comments

@KotlinIsland
Copy link
Contributor

KotlinIsland commented Nov 11, 2021

If the duck typed types were actually unions of all the ducklings then it would fix a bunch of edge cases that arise.
And would make this feature more easily understood and discovered.

def func1(c: complex):
    reveal_type(c)  # float | int | complex

def func2(f: float):
    reveal_type(f)  # float | int

def func3(b: bytes):
    reveal_type(b)  # bytes | bytearray | memoryview

def func4(u: unicode):  # python 2 moment
    reveal_type(u)  # unicode | str

edge cases:

def func(f: float):
    f.is_integer()  # error: int has no member "is_integer"
def func(f: float):
    if not isinstance(f, float):
        print("hi")  # no 'unreachable code' error
        reveal_type(f)  # int | complex

It would also be useful to have type types for float and bytes, when you want exactly float and don't want any stinking ints.

Related #11511, #11145

@CarliJoy
Copy link

CarliJoy commented May 20, 2022

See also: #12824 and #12643

@erictraut
Copy link

This feature request appears to be based on a misunderstanding of the Python type system. The type annotation float doesn't simply refer to the class float. It represents a set of types that includes float and all subtypes thereof. The type float and the type float | int refer to the same set of types, so they are equivalent.

@KotlinIsland
Copy link
Contributor Author

This feature request appears to be based on a misunderstanding of the Python type system.

That's obviously not what I am saying lol. This issue is regarding addressing the problems introduced by the 'ducktyping' functionality defined in pep 484, which states that float/int should lie about their base classes (mypy extends this idea to bytes). The solution being that instead of lying about their bases (extremely confusing), these specific types should just expand to a union of the ducklings.

@erictraut
Copy link

I don't understand what you're asking for here. I'm also not sure that you understood my previous point, so let me try again.

In type theory, if you have a class Parent and a class Child that derives from parent, then the types Parent and Parent | Child are 100% equivalent. If a type checker ever treats these as different, then it's a bug. If I understand you correctly, you're saying that float should be "expanded to" float | int, but those two types are 100% equivalent already because int is (by definition in PEP 484) a subtype of float. If mypy ever treats float differently from float | int, then it's a bug.

Are you suggesting that mypy add a new mode where it deviates from the behavior specified in PEP 484 and treats int as though it's not a subtype of float, etc.? That would be incompatible with all existing type stubs, so I don't think it would be very useful.

Or perhaps what you're asking is for new special forms to be added to the type system that represent "a float that is not an int" (float & ~int), etc.? That would require a new specification in the form of a PEP, and the mypy issue tracker wouldn't be the right place to request such a change.

Or perhaps what you're saying is that when mypy prints the type builtins.float in a textual form (e.g. in error messages), you always want it to print builtins.float | builtins.int. That would be sound from a type perspective, but I think most mypy users would find that representation to be redundant, verbose, and even somewhat confusing.

Or perhaps I haven't yet captured what you're proposing. If that's the case, can you provide more specifics?

@ikonst
Copy link
Contributor

ikonst commented Aug 27, 2023

Edit: Now I get it. I totally missed the fact the hierarchy is fake for those very specific types. Been living too long on mypy-island.

I think @KotlinIsland wants

  1. the fake hierarchy undone, and
  2. for float, emit float | int behind the scenes, and
  3. some time later introduce typing.FloatForReal that won't have that union-emitting behavior

Probably subject for a typing issue...

@KotlinIsland
Copy link
Contributor Author

@ikonst yeah, exactly. I plan on implementing this in basedmypy, it will be an interesting experiment.

@erictraut
Copy link

@ikonst, what do you mean by "emit float | int behind the scenes"? Will that result in any user-visible behavior change in mypy? If so, how? Is the only visible change that the textual output for reveal_type and error messages becomes more verbose?

@KotlinIsland
Copy link
Contributor Author

@ikonst, what do you mean by "emit float | int behind the scenes"? Will that result in any user-visible behavior change in mypy? If so, how? Is the only visible change that the textual output for reveal_type and error messages becomes more verbose?

No, annotating something with float will result in the type being float | int. And the promotion/ducktyping will be removed from int.

@erictraut
Copy link

Ah, I see what you mean. Thanks for the explanation. Yeah, that's an interesting experiment. Let us know what you find.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants