question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature request: infer function return types (or argument-types)

See original GitHub issue

Suggestion

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

  1. A way to create a type for a function with specified arguments but inferred return value.
  2. 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:open
  • Created 3 years ago
  • Reactions:5
  • Comments:14 (8 by maintainers)

github_iconTop GitHub Comments

4reactions
ackvfcommented, Apr 27, 2021

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 and typeFunctionReturnValue and I also took the liberty of renaming them.

type Args = [string, number]
const withParameters = <R>(fn: (...args: Args) => R) => fn
const myFunction1 = withParameters((a, b) => 'x') // (args_0: string, args_1: number) => string

const r = myFunction1('a', 2) // string


type Ret = {n: number}
const withReturnType = <A>(fn: (args: A) => Ret) => fn
const myFunction2 = withReturnType((x: string) => ({n: 42})) // (args: string) => Ret

const r2 = myFunction2('a') // Ret

IIUC the proposed approach should shorten it to this.
Also I will use the word defer instead of infer for no reason.

type WithParameters = (...args: Args) => defer // The args type is locked. The implementation will provide return type.
type WithReturnType = (...args: defer) => Ret // The return type is locked. The implementation will provide parameters type.

const myFunction1: WithParameters = (a, b) => 'x' // (args_0: string, args_1: number) => string
const myFunction2: WithReturnType = (x: string) => ({n: 42}) // (args: string) => Ret

So what does it do really…

const myFunction1: WithParameters = (a: number, b) => 'x' // Error: nope `a` is string
const myFunction2: WithReturnType = (x: string) => ({x: 42}) // Error: nope `n` is missing
3reactions
RyanCavanaughcommented, Jan 6, 2021

@andrewbranch is apparently faster at comprehending code samples than I am 😅

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - More on Functions - TypeScript
Its inferred return type is Type , but firstElement2 's inferred return type is any because TypeScript has to resolve the arr[0] expression...
Read more >
Can TS automatically infer return type of function generic ...
I'm a bit puzzled by the way that typescript infers return type of function generic parameters. In the example below I expect x...
Read more >
Functions - F# | Microsoft Learn
As in a regular function definition, the argument types can be inferred or specified explicitly, and the return type of the lambda expression...
Read more >
Typescript: Type Inference on function arguments (huge update)
In above example 'a' & { readonly __tag: unique symbol } is assignable to type argument of fn function but as you might...
Read more >
Generic Methods - Java™ Tutorials
Notice that we don't have to pass an actual type argument to a generic method. The compiler infers the type argument for us,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found