@oscarbenjamin, thank you for your very detailed reply.
You’re completely right about this. In practice, there obviously is the f.dispatch
pattern, where you import a function and then extend it explicitly. I’ve also seen a global pattern, where @dispatch
identifies functions by name. In this more global pattern, if
# a.py
from typing import dispatch
@dispatch
def f(x: int):
pass
# b.py
from typing import dispatch
@dispatch
def f(x: str):
pass
# c.py
from typing import dispatch
import a
import b
@dispatch
def f(x: float):
pass
# Now `f` has all three methods for `str`, `float`, and `int`, because `@dispatch`
# aggregates methods by name: `f`.
That is, you’re right.
This would be one possible mechanism.
The global pattern can be generalised to “namespaces”, where dispatch = Dispatcher()
, and this @dispatch
aggregates methods by name of the function.
The reason why I didn’t consider runtime initially is that I wasn’t completely sure what would be feasible and sensible from a type checking point of view. For example, the global pattern would be extremely difficult if not impossible to implement, because the type checker doesn’t know which files define methods.
I’m suspecting that, in practice, there might be always be a discrepancy between runtime and type checking. However, we might be able to mitigate these discrepancies by coming up with a mechanism and associated conventions that are reasonable from both a runtime and type checking point of view. For example, this sounds very reasonable to me:
The convention could be that, whenever you want to type check a function that uses multiple dispatch, you need to make sure that all methods that you’re using can be found with explicit imports of the form from module import f
. The type checker could then dig into the imports and find all relevant methods. Perhaps this runs into computational issues…
It depends on how you approach it. One approach is to say that, regardless what comes before, whenever you @dispatch
a function named f
, you add a method for a global function f
. To access that global function f
, you would need a reference to an f
that was previously @dispatch
ed. In your example, with this global mechanism, the first print statement would call the implementation something_else(str)
, and the print statement would call the implementation f(int)
in a
. If a
does not implement this, it would raise an error. The function f as something_else
imported from a
wouldn’t do anything, because it would be overwritten by the method for the global version of something_else
.