Home > database >  Function mixing generic type sources in Typescript
Function mixing generic type sources in Typescript

Time:10-03

I am trying to build a function to can receive 2 generic types, one from the call itself and the other one from the variable.

However, it looks like I can either call it without any type or with 2 types but I cannot mix both techniques.

I would like to be able to do something like this:

const bar: string = "";

const foo = <A,B>(arg: B): B => {
  const sideEffect: A = 0; // Do some stuff here
  return arg;
}

foo(bar); // OK
foo<number>(bar); // FAIL: Expected 2 type arguments, but got 1.ts(2558)
foo<number,string>(bar); // OK

CodePudding user response:

One thing you should to consider when you want to write a function in typescript:

  • your generic types should be binded with values. In fact, generic parameter is infered type of provided argument. It is unsafe to use generic without values. See example:
const foo = <A,>() => {
    const sideEffect: A = 0; // error
    return sideEffect;
}

foo<number & { name: 'John' }>().name // BOOM! Runtime error

As you might have noticed, number & { name: 'John' } is assignable to constraint number but it might lead to unexpected behavior. This is why you have an error here const sideEffect: A = 0; // error.

See this answer for more explanation and my article

Let's reimplement this function:

const bar: string = "";

const foo = <A extends number, B>(arg: B, a: A): B => arg

foo(bar, 0); // OK
foo(bar); // fail, expects two arguments

There is one exception of this rule. You may be forced to provide explicit generic parameter if your function expects an array:

import React, { useState } from 'react'

const App = () => {
    const [state, handleState] = useState([])

    const stateIsNever = state // never[]
}

In order to type state, you should provide explicit generic parameter:

import React, { useState } from 'react'

const App = () => {
    const [state, handleState] = useState<number[]>([])

    const stateIsNumber = state // number[]
}

SUMMARY

Each generic parameter should be binded with function argument.

For instance, take a look at Haskell or F# type systems.

module Module =
    // val foo : a:'a -> 'a
    let withArg a = a
    // val withoutArg : unit -> unit
    let withoutArg () = ()

There is a generic parameter a which was automaticaly created for withArg function.

Using generics without values it is like to have economic system without gold.

If you still want to use generics without values, like here:

const foo = <A,>() => {
    const sideEffect: A = 0; // error
    return sideEffect;
}

you should be aware that it is unsafe and TS system will not help you much to avoid runtime errors.

Even more, you should write your function in a way when you are not required to provide any generics during function call. Please see my article about function argument inference

P.S. Please take a look on this answer. There are a lot of generics, but you don't neet to provide each generic when you call a function. TS is able to infer each of them

CodePudding user response:

Since the compiler can not understand, you can set default type for B.

const bar: string = "";

const foo = <A,B=any>(arg: B): B => {
  const sideEffect: A = 0; // Do some stuff here
  return arg;
}

foo(bar); // OK
foo<number>(bar); // FAIL: Expected 2 type arguments, but got 1.ts(2558)
foo<number,string>(bar); // OK
  • Related