If we ignore hybrid (kw-or-positional) parameters¹, a decorator/higher-kinded-function can do one of the following:
- prepend/remove-from-front positional arg(s) (already supported)
- append/remove-from-end positional arg(s) when there’s no varargs
- add/delete keyword-only arg(s)
The supported use supports excluding functions from being used, e.g. def decor(f: Callable[Concatenate[int, P], R]) -> ... expresses “f needs to have at least one positional argument, and its first positional argument needs to be of type int”.
For keyword-only parameters, we just use name instead of position. So I don’t see how removing positionals from the end or, adding/removing keyword-only args are in any way more complex.
# pop first pos argument of type int.
# Needs≥1 arg, first arg to be int
def pop0_pos(f: Callable[Concatenate[int, P], R]) -> Callable[P, R]: ...
# pop last pos argument of type int.
# Needs≥1 arg, no varargs, last arg to be int
def pop_pos(f: Callable[Concatenate[P, int], R]) -> Callable[P, R]: ...
# pop keyword arg “size” of type int. (placeholder syntax, don’t worry about it)
# Needs a kwarg named “size” of type int
def pop_kw(f: Callable[Concatenate[P, "size" : int], R]) -> Callable[P, R]: ...
The only case where prepending is simpler than the rest is when Concatenate appears in a return-type-only position, but again, we already see it having requirements for the function’s signature in all other cases, so I don’t see why that would be special:
# prepend pos argument of type int.
# No requirements
def prepend_pos(f: Callable[P, R]) -> Callable[Concatenate[int, P], R]: ...
# append pos argument of type int.
# Needs no varargs
def append_pos(f: Callable[P, R]) -> Callable[Concatenate[P, int], R]: ...
# add kwarg “size” of type int.
# Needs `f` to not have an argument named “size”.
def add_kw(f: Callable[P, R]) -> Callable[Concatenate[P, "size" : int], R]: ...
Things like replacing the type of a keyword argument works the same way as replacing the type of the first positional argument, e.g.:
def replace_arg0(f: Callable[Concatenate[int, P], R])
-> Callable[Concatenate[str, P], R]:
def replace_kwarg(f: Callable[Concatenate[P, "size" : int], R])
-> Callable[Concatenate[P, "size" : str], R]:
¹I don’t think it’s valuable to expand the use of keyword-or-positional arguments, so I think it’s fine to reduce complexity by only having syntax for adding/removing positional-only and keyword-only arguments