Polimorfni tip povratne vrijednosti u TypeScriptu


Recimo da želimo implementirati hijerarhiju tipova podataka, koji tip bi trebao biti korišten?

interface Collection<T> {
  map(f: (v: T) => T): ???
}

interface Array<T> extends Collection<T> {
  map(f: (v: T) => T): ??? { return new Array<T>() }
}

interface LinkedList<T> extends Collection<T> {
  map(f: (v: T) => T): ??? { return new LinkedList<T>() }
}

Polimorfiziranje this ne radi

interface Collection<T> {
  map(f: (v: T) => T): this
}

interface Array<T> extends Collection<T> {
  map(f: (v: T) => T): this { return new Array<T> }
  // ^ error `Error: type Array<T> is not assignable to type this`
}

Odgovor #1

Iskrena upotreba polimorfnog this je kada zapravo vraćate this. Razmotrite sljedeće klase

export interface Collection<T> {
  map(f: (v: T) => T): this
}

class Array<T> implements Collection<T> {
  map(f: (v: T) => T): this { return new Array<T>() as this; }
}

class MyArray<T> extends Array<T>{ }

new MyArray().map(o=> true) instanceof MyArray // false, but map returns MyArray

Ništa ne prisiljava MyArray da prepiše map i vrati ispravnu instancu, tako da kada pozovemo map na MyArray i dalje dobivamo instancu Array (možda donekle iznenađujuće, rekao bih).

To rečeno, ne postoji dobar način da se ovo modelira u sustavu tipova, to je klasa koja, kada se naslijedi, mora implementirati određenu metodu bez da klasa bude apstraktna.

Možete koristiti polimorfni this i koristiti svojstvo constructor kako biste se zaštitili od gore navedenog scenarija, ali ne postoji način da se ograniči izvedeni konstruktor da nema parametre (kao što bi poziv u nastavku zahtijevao). Dakle, ovo rješenje nije potpuno tip sigurnosno, i koristi određenu vrstu tvrde provjere tipa da bi posao bio obavljen, ali mislim da je to koliko je sigurno moguće:

 interface Collection<T> {
  map(f: (v: T) => T): this
}

class Array<T> implements Collection<T> {
  map(f: (v: T) => T): this { return new (this.constructor as any)(); }
}

class MyArray<T> extends Array<T>{ }

console.log(new MyArray().map(o=> true) instanceof MyArray) // ture

Odgovor #2

Ako dobro razumijem, ono što tražite je f-ograničena polimorfija („preopterećenje this“).

interface Collection<T> {
  map(this: Collection<T>, f: (v: T) => T): this;
}

Tip this uvijek se koristi kao prvi parametar. Primijetite da možemo dodati više preopterećenja. Na primjer, za pretvaranje Collection<number> u Collection<string>, mogli bismo napraviti:

interface Collection<T> {
  map(this: Collection<T>, f: (v: T) => T): this;
  map<U>(this: Collection<T>, f: (v: T) => U): Collection<U>;
}

Upotreba :

declare const collection: Collection<number>;

collection.map(x => x.toString())