Hi typing-sig, I’d like to start a discussion about moving forward with improved syntax for typing.Callable based on discussion in the Typing Summit: * Allow `(ArgType0, ArgType1) -> ReturnType` * In place of `Callable[[ArgType0, ArgType1], ReturnType]` A lot of folks also want a way to express things like named, optional, and variadic args, but this is more complicated and has harder tradeoffs. I'll start a separate thread next week to discuss that, let's aim to keep this thread focused on the syntactic sugar described above. If we think this is a good idea, I'm happy to work on drafting a PEP. ## Background The existing Callable type is useful for simple functions with required arguments passed by position, but there’s consensus that it can be hard to keep track of brackets, especially as the types get complex. For example, even in very simple functional-style code with higher-order functions the brackets can be cumbersome: ``` from typing import Callable def accepts_callback(callback: Callable[[str, int], str]) -> None: print(callback("answer", 42) def f(s: str, x: int) -> str: return s + ": " + str(x) accepts_callback(f) accepts_callback(lambda s, _: s) ``` In agreement with a poll from the Typing Summit, I’d like to propose allowing * `(Argtype1, Argtype2) -> ReturnType` to be used in place of * `Callable[[Argtype1, Argtype2], ReturnType]` This allows us to rewrite the example above as: ``` def accepts_callback(callback: (str, int) -> str) -> None: print(callback("answer", 42) def f(s: str, x: int) -> str: return s + ": " + str(x) accepts_callback(f) accepts_callback(lambda s, _: s) ``` Advantages of arrow syntax include: * eliminate the need to import Callable * easier-to-skim types * they are shorter, and one of the problems with Callables is just verbosity * the parentheses and arrow have more visual structure than existing bracket-based Callable syntax * a syntax that should feel familiar: * it mimics the syntax for ordinary functions and stubs * it is similar to several other languages e.g. * [typescript](https://basarat.gitbook.io/typescript/type-system/callable#arrow-syntax) * [flow](https://flow.org/en/docs/types/functions/#toc-function-types) * [kotlin](https://kotlinlang.org/docs/lambdas.html#function-types) ## Open questions about syntax rules Would we require parentheses around a callable return type as above, or allow it to be bare (which causes two ->s to be visually at the same level of the code, even though one is nested in the AST)? Consider this function (annotated using a conservative syntax where we wrap the return type in paranetheses): ``` def identity( f: (str, int, int) -> int, ) -> ((str, int, int) -> int): return f ``` In this case, would we want to * require parentheses around a callable return type as above, or * allow it to be bare (which causes two ->s to look a little bit as if they are at the same code level) Here’s what the example above would look like if we allow bare arrow syntax in the return type: ``` def identity( f: (str, int, int) -> int, ) -> (str, int, int) -> int: return f ``` Another option, if we feel like the outer parentheses are necessary would be to make the parameter grouping optional in this case, for example: ``` def identity( f: (str, int, int) -> int, ) -> (str, int, int -> int): return f ``` ## Alternatives considered We ran a poll at the typing summit comparing three options for nicer Callable syntax: * This proposal `(ArgType1, ArgType2, ArgType3) -> ReturnType` * 72% of participants upvoted this * A typescript-like syntax `(a: ArgType1, b: ArgType2) => ReturnType` * 62% of participants upvoted this * Disadvantages are * a new symbol `=>` * argument names are nuisance parameters for positional args (which is all the existing Callable supports) * `[ArgType1, ArgType2, ArgType3] -> ReturnType` * 8% of participants upvoted this * The main disadvantage is that brackets don’t seem as intuitive - Steven