简体   繁体   中英

Is there a way to express interface function polymorphism when the function will always receive the type it's defined on?

I was hoping to define an interface hierarchy, where the base interface declares a function, and each extension's version of that function receives its own type (rather than the base type). Minimally, I tried:

interface IBase {
  a: string,
  f: (x: IBase) => any // Cause of the problem
}

interface IExtension extends IBase {
  b: string,
}

const f1 = (x: IExtension) => //... typechecks when using x.b

const ext1: IExtension = {
  a: "a1",
  b: "b1",
  f: f1 // This line doesn't typecheck because IExtension is not strictly IBase
}
  

The type error:

Type '(x: IExtension) => {}[]' is not assignable to type '(x: IBase) => any'

Digging around, I saw this answer regarding strictFunctionTypes . Making the following changes causes the program to typecheck, because methods aren't subject to strictFunctionTypes , and therefore allow bivariance:

interface IBase {
  a: string,
  f(x: IBase): any
}

Edit: as explained in the comment by @jcalz , this approach is blatantly unsound. It also doesn't capture the constraint that f is called with the type it's defined on.

Is there a way express this typing in TypeScript? Something like:

interface IBase {
  a: string,
  f: (x: IBase | * extends IBase) => any
}

I haven't been able to find anything like that that avoids generics. I understand that generics could be used here, but I won't be walking that route, especially given the method syntax works as expected. Really appreciate any additional insight on this topic!

It looks like you want to use the polymorphic this type , which is sort of an implicit generic type referring to the current type:

interface IBase {
  a: string,
  f: (x: this) => any
  //     ^^^^
}

Then when you extend IBase , the f method of the extensions will automatically refer to the extensions and not IBase :

interface IExtension extends IBase {
  b: string,
}

type IXF = IExtension['f'];
// type IXF = (x: IExtension) => any

const ext1: IExtension = {
  a: "a1",
  b: "b1",
  f: x => x.b.toUpperCase()
}

and

interface ISomethingElse extends IBase {
  z: number
}

type ISF = ISomethingElse['f']
// type ISF = (x: ISomethingElse) => any

const sth2: ISomethingElse = {
  a: "a2",
  f: s => s.z.toFixed(),
  z: 123
}

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM