Reputation: 265
Say I'm interested in a generic function that will give a compile time error if called with arguments of different types.
Initially, I've tried something like that:
type genericFunc = {
<T>(a:T, b:T): boolean
}
let isEq: genericFunc = (a,b) => { return a === b }
When I try to call it like so:
isEq(1,"str")
I get a compile time error, as expected. However, when I try to:
isEq(1, null)
or:
isEq(1, undefined)
It all compiles without complaining. Per vscode, the type of isEq(1, null)
is inferred to as:
let isEq: <number | null>(a: number | null, b: number | null) => boolean
Setting "strictNullChecks": true
in my tsconfig.json does not change that.
To summarize, I'm interested in the following questions:
genericFunc
differently?Many Thanks!
Upvotes: 0
Views: 185
Reputation: 187054
T
can be a union, so it could always just be a union of the two argument to satisfy the function's type.
Though I'm not really sure why isEq(1, "str")
fails since it seems like number | string
seems like it should be the inferred type there, but helping out the compiler with a isEq<number | string>(1, "str")
does seem to allow it there. I wish I could tell you why that happens.
An improvement would be to have two generic parameters, one for each argument, and ensure that one extends the other.
<T, U extends T>(a:T, b:U): boolean
Now U
must be the same type (or a subtype) of T
in order to be allowed.
// works
isEq('asd', 'qwe')
// All type errors:
isEq(1, "str")
isEq(1, null)
isEq(1, undefined)
All that said, the "same" type is a trickier thing than you might expect.
This solution would allow:
isEq({a: 234}, {a: 123, b: 456})
Because the second argument is a subtype of the first. Meaning it would be assignable to that type. Which raises the question:
Are these the same type?
const a = { a: 123 }
const b = { a: 456, b: 789 }
How about these?
type Test = { a: number, b?: number }
const a: Test = { a: 123 }
const b: Test = { a: 456, b: 789 }
Or these?
class A { public num: number }
class B extends A { public str: string }
Or?
const a: string | number = 123
const b: string | number = 'asd'
Because all these would be considered equal by this approach, because b
in each case is assignable to the the type of a
. Maybe that's fine, maybe it's not.
How to compare these types really depends on how you intend to use this.
Upvotes: 1