Hi,
Context: during the typing meeting of this week Brandon presented the idea of introducing a Compose type operator, which takes a variadic of callables, checks that they compose, and returns the type of the composed function.
To follow-up on the discussion about Compose:
We were wondering what can be achieved nowadays without custom operators, just relying on overload. Let's think about the example of a compose function, we could do the following right?
from typing import Callable as Call
# Overloads
def compose(f1: Call[A,B]) -> Call[A,B]
def compose(f1: Call[A,B], f2: Call[B,C]) -> Call[A,C]
def compose(f1: Call[A,B], f2: Call[B,C], f3: Call[C,D]) -> Call[A,D]
...
What is less obvious is how would we type a class like torch.nn.Sequential, which would look like this with the Compose operator:
class Sequential(Generic[A,*Ts]):
def __init__(self, fs: *Ts)
def __call__(self, in: A) -> Compose[*Ts]
Here we can't overload the class definition, so what can be done? We had a couple of ideas but they would not really work for a variable number of arguments because we can't overload. An example of those approaches would be:
class Sequential(Generic[A,B,C]):
def __init__(self, f1: Callable[A,B], f2: Callable[B,C]): ...
def __call__(self, in: A) -> C: ....
Best,
Alfonso.