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