coady / multimethod

Multiple argument dispatching.
https://coady.github.io/multimethod
Other
284 stars 23 forks source link

Counterintuitive dispatch when using Union with subclasses #24

Closed plammens closed 3 years ago

plammens commented 3 years ago

If one of the registered functions of a multimethod targets a certain type A and another function targets the Union of several subclasses of A, e.g., B and C, the former will always be preferred (and the latter ignored), even if calling the multimethod with instances of B or C. I would have expected the latter to take priority in this case, since I think of the union function as several functions, each targeting one type in the union, but all with the same code (so I use Union to avoid repeating myself).

Example

from typing import Union

class A: pass
class B(A): pass
class C(A): pass

@multimethod
def f(x: A): print("A")

@f.register
def f(x: Union[B, C]): print("B, C")

Expected

>>> f(A())
A

>>> f(B())
B, C

>>> f(C())
B, C

Actual

>>> f(A())
A

>>> f(B())
A

>>> f(C())
A

Cause

From what I understood from the docs, a Union is checked directly with isinstance, and a tie such as in the last two examples above is resolved using __mro__, but since the __mro__ of the Union will be completely different this won't work as expected.

Workarounds