DEV Community

Pragmatic Maciej
Pragmatic Maciej

Posted on

Advanced TypeScript Exercises - Answer 4

The question was about creating utility generic type which will be able to get any function type and create a new type which will have one argument more at the front.

Solution 1: Using Parameters and ReturnType utility types

type AppendArgument<F extends (...args: any) => any, A> 
 = (x: A, ...args: Parameters<F>) => ReturnType<F> 

// using
type FinalF = AppendArgument<(a: number, b: string) => number, boolean> 
// FinalF is (x: boolean, a: number, b: string) => number 👍
Enter fullscreen mode Exit fullscreen mode

What we did here:

  • first argument F is narrowed to only function types (...args: any) => any, such narrowing is crucial in order to use Parameters and ReturnType
  • we create a type which first argument is type A and other arguments are spread by ... from the original function by Parameters which produce arguments types of the original type
  • we say that our new type returns the same thing as the original was by ReturnType

Solution 2: Using infer

type AppendArgument<F, A>
  = F extends (...args: infer Args) => infer Return
  ? (x: A, ...args: Args) => Return
  : never

// using
type SomeF = (a: number, b: string) => number
type FinalF = AppendArgument<SomeF, boolean> 
// FinalF is (x: boolean, a: number, b: string) => number 
Enter fullscreen mode Exit fullscreen mode

The second proposition isn't using any utility types, but we use directly infer which is a tool for getting types from generic types, and function is this kind of a type, parameterized by two parameters - arguments type and return type.

Note we can understand utility types ReturnType and Parameters as types abstraction which are using infer in the definition, so they are kind of abstraction over construct we did in solution 2

What we did here:

  • we infer all arguments by ...args: infer Args and return type by => infer Return
  • we are putting newly obtained types in the definition, with putting as first argument type A exactly as we did in solution 1
  • take a look that we needed to use conditional type, as this is the only way we can work with infer

If you want to know more about infer check the Answer 1 from the series

This series is just starting. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter.

Top comments (3)

Collapse
 
kolharsam profile image
kolharsam

I would like to use unknown over any in AppendArgument. Because I would prefer not to get past type checking using any.

type AppendArgument<F extends (...args: unknown[]) => unknown, A> =
    (x: A, ...args: Parameters<F>) => ReturnType<F>;

type FinalF = AppendArgument<(a: number, b: string) => number, boolean>;  // this gives an error since the function parameters cannot be mapped to `unknown`
Enter fullscreen mode Exit fullscreen mode

Any ideas of how I can change this?

Collapse
 
macsikora profile image
Pragmatic Maciej • Edited

Extends any[] doesn't mean it is any[] it only means it is any array, so array with any type you want. Compiler will infer correct type and it will never be any. There is no type whole here

Collapse
 
conghuynhho profile image
Hồ Công Huynh
type AppendArgument2<F, A> = 
     F extends (...args: infer Args) => infer Return 
           ? (...args:  [...Args, A]) => Return 
           : never;
Enter fullscreen mode Exit fullscreen mode

We can also create new arguments like this:
(...args: [...Args, A])