#Overload Interface Function Signature With `type`

16 messages · Page 1 of 1 (latest)

vivid ravine
#

I have a function signature that is used across multiple interfaces and sufficiently complex for me to put into a separate type definition:

type LongSignature = (a: string, b: string, c: string, d: string, e: string, f: string) => number

When I try to use that signature for an overload I get an error:

interface F {
    f (a: string): number
    f: LongSignature  // Subsequent property declarations must have the same type.  Property 'f' must be of type '(a: string) => number', but here has type 'LongSignature'.(2717)
}

One way to overcome this is to rewrite all methods as arrow functions and express the overloads via intersection type:

interface G {
    g: ((a: string) => number) & LongSignature
}

As the actual code is far more complex and has a lot of overloads, this becomes ugly really quick. So I'd really prefer the syntax I presented for F. Can this be done?

Side note: the functions within each interface have the same signature but different names (see F.f vs G.g). So I can not resolve this via inheritance.

lilac elbow
#

There's no other way.

#

But I mean, you can't really get easier than A & B right?

#
type Foo = {
    fn: FnOverload1 &
        FnOverload2 &
        FnOverload3 &
        FnOverload4 &
        FnOverload5
}
vivid ravine
#

It just gets a tad more verbose

interface F {
  f: LongSignature
  f(predicate: object): this
  f(...predicates object[]): this
  f(...predicate object[], flag: boolean): this
  f<T>(expression: T): this
}

vs

interface F {
  f: LongSignature
    & ((predicate: object) => this)
    & ((...predicates object[]) => this)
    & ((...predicate object[], flag: boolean) => this)
    & (<T>(expression: string) => this)
}

probably a lot of preference here, but the second variant looks much more cluttered. Which I was trying to avoid

lilac elbow
#

I suppose it is a bit subjective.

#

On a somewhat related note, methods (f(...): ...) has soundness issues comparing to arrow functions (f: (...) => ...), so that's to be avoided imo.

vivid ravine
#

in what way?

lilac elbow
#

Methods are bivariant.

vivid ravine
#

and arrow functions are not?

lilac elbow
#

Correct, arrow function has proper variance.

#

Compare the following two examples:

slow meteorBOT
#
type Foo = {
    fn(arg: string | number): void
}

const foo: Foo = {
    // This should not be allowed
    fn(arg: string) {
        arg.toUpperCase()
    },
}

foo.fn(42) // Explode at runtime
#
type Bar = {
    fn: (arg: string | number) => void
}

const bar: Bar = {
    fn(arg: string) {
//  ^^
// Type '(arg: string) => void' is not assignable to type '(arg: string | number) => void'.
//   Types of parameters 'arg' and 'arg' are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.
        arg.toUpperCase()
    },
}

bar.fn(42)
lilac elbow
#

It's correctly caught only with arrow function.

vivid ravine
#

that is interesting and also a bit unexpected. I guess I'll note it down as an added benefit for using the intersection type... in face of absence of alternatives.

Anyway, I'll stick with the intersection type then. Thanks once more for the insight and help. !karma+ Burrito