Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: typing only the arguments or the return type of a function

Tags:

I'm having several functions that accept different arguments but all of them return a function with the same signature:

const createSum5 = () => (c: number) => c + 5;
const createMultiplyN = (n: number) => (c: number) => n * c;
const createWordsSum = (word: string) => (c: number) => word.length + c;

The return type is always (c: number) => number. Is there a way that I can type this return value for all the functions, without typing the argument? So that I could short them to this without losing type safety:

createSum5: /* Something here */ = () => c => c + 5;


I've tried these, but:

1.This solution loses type-safety: type NumericArg = (...args: any) => (c: number) => number;

2.I could write the function signature for every function in advance:

type Nfunction = (c: number) => number;
type createSum5Type = () => Nfunction;
...
const createSum5: createSum5Type = () => c => c + 5;

But that would be tedious. I want Typescript to automatically infer the arguments as it would do if I don't specify the types.

Is there any other way? Can the opposite be done (specifying functions with the same signature but let Typescript infer the return type?).


EDIT #1: Here's an example when I speak about the opposite:

const func1: /* Something here */ = num => '#'.repeat(5);
const func2: /* Something here */ = num => num * num;

Both func1 and func2 have the same signature (num: number) but different return types. I want Typescript to infer that every time I call func1 the return type is a string, but with func2 the return type is a number.


EDIT #2:

My use case is not relevant, but I'll add it anyway. I'm developing a React-redux application using Thunks. Every thunk always returns a function with signature (dispatch, getState) => void but they may accept different parameters. After some research this is the less verbose version that I could find:

const onFetchUser = (userIds: number[]) : ThunkFunction = dispatch => { ... }.

If there was a way to allow Typescript to infer the arguments but let me set the return type (which is my question here), I could make it easier to read like this:

const onFetchUser: /* something here */ = (userIds: number[]) => dispatch => {...}.

like image 839
clovis1122 Avatar asked Feb 22 '19 12:02

clovis1122


2 Answers

Not sure if it is the only option, but one option is to use a helper function that has a generic parameter that will capture the actual type of the function passed in but that will also enforce a return type of (c: number)=> number.

function fn<T extends (...a: any[]) => (c: number) => number>(o: T) {
  return o;
}
const createSum5 = fn(() => c => c + 5)
const createMultiplyN = fn((n: number) => c => n * c);
const createWordsSum = fn((word: string) => c => word.length + c);

I don't believe another option exists, typescript does not in general allow partial inference for variables (or more specifically constrained inference) this can only be done with a function.

like image 100
Titian Cernicova-Dragomir Avatar answered Oct 19 '22 08:10

Titian Cernicova-Dragomir


TypeScript supports the keyword infer, which would allow you to preserve the types of arguments and/or return types of a function.

For conditional types, it would look like this:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

There is some info about infer here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

Update:

You can do it just by using a generic function:

type numFunc<T> = (arg: T) => (c: number) => number; 

const createSum5: numFunc<void> = () => (c: number) => c + 5;
const createMultiplyN: numFunc<number> = (n: number) => (c: number) => n * c;
const createWordsSum: numFunc<string> = (word: string) => (c: number) => word.length + c;

const createSumString: numFunc<number> = () => (c: number) => 'Hello';  //error
like image 24
Yakov Fain Avatar answered Oct 19 '22 10:10

Yakov Fain