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)
Playground Link
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.