Reputation: 15336
Let's say we want to implement data types hierarchy, what type should be used?
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>() }
}
Polymorhping this
is not working
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`
}
Upvotes: 2
Views: 1459
Reputation: 25790
If I understand correctly, what you're looking for is f-bounded polymorphism (“overloading this
”).
interface Collection<T> {
map(this: Collection<T>, f: (v: T) => T): this;
}
The type of this
is always used as the first parameter. Notice we can add more overloads. For example, to transform a Collection<number>
into a Collection<string>
, we could do:
interface Collection<T> {
map(this: Collection<T>, f: (v: T) => T): this;
map<U>(this: Collection<T>, f: (v: T) => U): Collection<U>;
}
Usage:
declare const collection: Collection<number>;
collection.map(x => x.toString())
Upvotes: 1
Reputation: 249636
The honest use of polymorphic this
is when you are actually returning this
. Consider the following classes
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
Nothing forces MyArray
to override map
and return a correct instance, so when calling map
on MyArray
we still get an instance of Array
(somewhat surprisingly I might say).
That being said there is not good way to model this in the type system, that is a class that when derived must implement a specific method without the class being abstract.
You could use polymorphic this
and use the constructor
property to protect against the above scenario, but there is no way to constrain the derived constructor to have no parameters (as the call below would require). So this solution is not fully type safe, and does use some type assertion to get the job done but I think it is as safe as it gets:
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
Upvotes: 1