Concatenate is only valid as the first argument to Callable or to a user-defined generic class with paramspec parameter
The last argument to Concatenate must be a ParamSpec or ...
I am wondering of whether Concatenate is actually needed here. Can’t we simply write Callable[Concatenate[Type1, Type2, P], ReturnType]as Callable[[Type1, Type2, P], ReturnType]? At first I thought that using a list in typing could be a problem but a list is already allowed as the first argument to Callable. Is this feasible or am I missing some context here like cases where the new syntax would be ambiguous? My motivation here is saving an import from typing and making callable annotations that need concatenate less boilerplate-y.
Concatenate is the only way to combine a ParamSpec with extra arguments. Lists are valid arguments to Callable, but lists that contain a ParamSpec are not.
To me, this makes semantic sense. A ParamSpec is supposed to encompass a full argument list. “A full argument list, then some more arguments” is confusing; it reads like “an argument list within an argument list”, which doesn’t meaningfully exist. “An argument list concatenated with some other stuff” is much more intuitive.
I suppose you could say the same thing about Concatenate[int, str, P], where the individual types and the Paramspec are both used as the same kind of type argument.
I think it’s pretty inconsistent that you use **P when declaring the paramspec as type parameter, but not when e.g. concatenating or when passing it directly to Callable.
I’d also like to see Concatenate go, with generic callables being expressed like Callable[[int, str, *Ts, **P], R]. Or, with a typed dictionary instead of a ParamSpec, Callable[[int, str, *Ts, **TD], R].
One wart is how ... would work with this syntax. Callable[[int, **...], R] feels a bit off to me, but maybe that’s just a gut reaction.
I think it does meaningfully exist, if you reframe it as “an argument list unpacked within an argument list”
Tuple/TVT[1] unpacking already supports this:
type Foo[*Ts] = Callable[[*tuple[int, str, *Ts]], ...]
But as Joren points out,
We are missing syntax to spell out ParamSpec concatenation as what would likely be
type Foo[**P] = Callable[[int, str, **P], ...]
(Since ** within a list is not valid)
Perhaps we could supersede the use of Concatenate by allowing ParamSpec (and ...) within Unpack[2]? (which would likely be the backported syntax if [**P] became a thing anyway?)
type Foo[**P] = Callable[[int, str, Unpack[P]], ...]
Notably, it does not save you an import, but it is slightly shorter.
type Foo[**P] = Callable[Concatenate[int, str, P], ...]
Exactly. I am suggesting that we change this to become valid and to mean what Concatenate means today. In a way this is a good thing because it means that there are no backwards compatibility issues.
Agreed, I guess we are in this situation because Callable and ParamSpec predate PEP 695 syntax.