Feature request: infer function return types (or argument-types)
See original GitHub issueSuggestion
A way to specify function arguments without specifying return types; and a way to specify return type without specifying arguments.
🔍 Search Terms
IOC, inversion of control, function arguments, argument type, function return value, infer
✅ Viability Checklist
My suggestion meets these guidelines:
- This wouldn’t be a breaking change in existing TypeScript/JavaScript code
- This wouldn’t change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript’s Design Goals.
⭐ Suggestion
- A way to create a type for a function with specified arguments but inferred return value.
- A way to create a type for a function with specified return value but inferred arguments.
Syntax: Keyword infer at LHS and RHS of function types.
type Both_inferred = infer => infer // this would be the explicit equivalent to not providing a type on declaration
type Rhs_inferred = ( arg: number ) => infer // only the return type is inferred
type Lhs_inferred = infer => number // only the arguments are inferred
type One_argument_inferred = ( arg1: infer, arg2: number ) => number // Only the first argument inferred
type Rest_arguments_inferred = ( arg1: number, ...rest: infer ) => number // Infer all arguments after the first one
This syntax needs not to be the only one considered, but hopefully it will illustrate what I am suggesting.
📃 Motivating Example
I will summarize the current situation, because human ingenuity is great and programmers find ways to circunvent limitations.
No safety
Right now we have:
type Function = (...args: any) => any
Which is basically the broadest function type you could have. It does not discriminate based on arguments or return type. This works and is marginally better than Javascript. But what if you wanted more type-safety?
Narrow arguments
Currently, when a developer wants to type the arguments of a function but infer the return types this is what I have seen them do:
const typeFunctionArguments = <R>(fn:(...args: ArgsType) => R) { return fn(...args) }
const myFunction = typeFunctionArguments((...args /*should be ArgsType*/) => {/* Implementation here */ }
Which is a hack and against typescript design principles. As current Typescript limitations leave developers no other choice than writing a runtime expression code to achieve typing. When seeking to assign a type to myFunction
, developers should not need to call a function wrapper. Especially, if the only purpose of the function wrapper is to circumvent the limitations of the typing system.
Narrow return value
Similarly, people also use an unecessary typing-function to type a functions with a narrow return type.
const typeFunctionReturnValue = <A>(fn:(...args: A) => ReturnType) { return fn(...args) }
const myFunction = typeFunctionReturnValue((/* Arguments */) => {/* Implementation that should return ReturnType */ }
Hacks that do not achieve the desired result
Developers sometimes do not care about typings being right and I have also seen the following hacks that do not achieve the correct type but do compile.
any
or unknown
Yes you can just use any
, but then you are throwing all type inference and type-safety out of the window.
type TypeFunctionArguments = (arg: ArgsType) => any
type TypeFunctionReturnValue = (...args: any) => ReturnType
Using unknown
might seem tempting, as it would provide type-safety. However, it is not really a functioning workaround on its own, as you would need to add typeguards or typecasts for it to compile. Additionally, you would still be throwing away all type inference:
type Function_that_takes_in_a_number = (arg: number) => unknown
const roundInferred = (arg: number) => (arg).toFixed(2);
const roundTyped: Function_that_takes_in_a_number = (arg: number) => (arg).toFixed(2);
roundInferred(1/3).replace('.', ',') // ✅ OK
roundTyped(1/3).replace('.', ',') // ❌ RHS Type inference overridden too, so it will throw: Property 'replace' does not exist on type 'unknown'
Type inference
A common non-solution is to not use a type and let the typing system infer. This of course provides little type-safety as you could end up writing the wrong type.
const functionThatShouldHaveTypedReturnValue =
(/* Arguments */) => {/* Implementation that is not guaranteed to return ReturnType */ }
Specifying the types
Another common non-solution is to specify the types of the arguments or the return type in the implementation:
const functionWithTypedArguments = (...args: MyGuessedTypes}) => {/* user-defined code here */}
This tends to be a source of error since there is no guarantee that MyGuessedTypes
and ArgsType
are the same type, and is also a maintainability hazard, as MyGuessedTypes
and ArgTypes
might start to stray farther and farther away from each other over time with every additional change.
💻 Use Cases
- Object oriented programming
- Functional programming
- Inversion of control
- Observer pattern
- Generic functions/classes/components that take in functions arguments
If you want more specific examples in any use-case category or code sandboxes, don’t hesitate to ask. Thanks for your time and reading all the way down here 👍
Issue Analytics
- State:
- Created 3 years ago
- Reactions:5
- Comments:14 (8 by maintainers)
Top GitHub Comments
Took me a while to understand this but I now see the benefit of it and it’s just amazing!
Anyway, I believe you have an error in your original sample code for
typeFunctionArguments
andtypeFunctionReturnValue
and I also took the liberty of renaming them.IIUC the proposed approach should shorten it to this.
Also I will use the word
defer
instead ofinfer
for no reason.So what does it do really…
@andrewbranch is apparently faster at comprehending code samples than I am 😅