Probably been asked and answered but I didn't find the answer - so I ask.
The workaround is not great as if I want to include more type it will not be as easy to upgrade. Any better way of doing it ?
declare const max: <A extends number | string> (a: A) => (b: A) => A
max (2) (3) // Argument of type '3' is not assignable to parameter of type '2'
max ('a') ('b') // Argument of type '"b"' is not assignable to parameter of type '"a"'
// Possible workaround but will become clumsy if possible type to extends grow
declare const maxClumsy: {
(a: number): (b: number) => number
(a: string): (b: string) => string
}
maxClumsy (2) (3)
maxClumsy ('a') ('b')
CodePudding user response:
Function overloads are the way to go here, I say. For just two types, your declaration seems fine:
declare const maxClumsy: {
(a: number): (b: number) => number
(a: string): (b: string) => string
}
But if you really are concerned with the repetition of string
and number
, then you can construct this type programmatically; function overloads are equivalent to an intersection of function types, so we can use a distributive conditional type and the UnionToIntersection
helper from this other Q&A to transform the union type string | number
into the desired type:
type CurriedUnion<T> = T extends any ? (a: T) => (b: T) => T : never
type UnionToIntersection<T> = (T extends any ? (a: T) => void : never) extends (a: infer U) => void ? U : never
type CurriedOverload<T> = UnionToIntersection<CurriedUnion<T>>
Usage:
// type Test = ((a: string) => (b: string) => string) & ((a: number) => (b: number) => number)
type Test = CurriedOverload<string | number>
declare const coolMax: CurriedOverload<string | number>
// OK
coolMax (2) (3)
// OK
coolMax ('a') ('b')
// error
coolMax (2) ('b')
// error
coolMax ('a') (3)
Be forewarned that distributing over union types like this can cause unexpected results when the input types are themselves unions; particularly, boolean
is defined as the union type true | false
, so this will not do what you want in that case.
CodePudding user response:
Following up on the comment from @matt-diamond
Keep in mind that you're passing in literals here... if the input variables are typed more broadly, you won't have an issue.
If you don't want to have to pass explicitly typed variables each time, you can use this method.
type Num = number | string
type AsNum<T> = T extends string ? string :
T extends number ? number : never
declare const max: <T extends Num>(a: T) => (b: AsNum<T>) => AsNum<T>
max(2,4) max('2','4') // OK
max(2,'4') // Argument of type 'string' is not assignable to parameter of type 'number'
max(true,false) // Argument of type 'boolean' is not assignable to parameter of type 'string | number'
Although it does look just as "clumsy" as your working suggestion, if not more.