I've seen questions that are somewhat similar to this but don't answer the specific question I have. I want to create a base class that users should be able to extend in such a way that the methods in my base class that return instances of the base class, return instances of the derived class when called from the derived class. This class is intended to be immutable, with a bunch of chainable methods. Here is a class just to illustrate the problem:
class Pair<T> {
constructor(public left: T, public right : T) {}
setLeft(left: T) {
return new Pair(left, this.right)
}
setRight(right: T) {
return new Pair(this.left, right)
}
map<R>(fun: (t: T) => R) {
return new Pair(fun(this.left), fun(this.right));
}
}
A simple way to extend it would be to just extend pair's prototype, but I don't think this can be done in another file (when I do interface Pair<T> { }
I get an error telling me "Import declaration conflicts with local declaration of Pair".
Another option is to create a derived class:
class MyPair<T> extends Pair<T> {
constructor(left: T, right: T) {
super(left, right)
}
swap() {
return new MyPair(this.right, this.left)
}
}
The problem here is that as soon as I call a Pair
method, I won't be able to call any MyPair
method since I'll get a Pair
(doing new MyPair(10, 20).setLeft(30).swap()
is not possible).
This suggestion shows how I could use an activator function to create instances of the derived class, something like this:
class Pair<T, R extends Pair<T, R>> {
constructor(private activator: (left: T, right: T) => R, public left: T, public right : T) {}
setLeft(left: T) {
return this.activator(left, this.right)
}
setRight(right: T) {
return this.activator(this.left, right)
}
}
That seems to kind of work, but something seems wrong with the fact that activator uses type T
for the arguments. These two lines give an error when I use them in a method inside Pair
:
var hi = new Pair(this.activator, 10, 20);
var bye = this.activator(10, 20);
The error says that number
is not assignable to type T
, so it seems the type of the arguments are bound to T
. I would need a way for my generic activator to stay generic, but that doesn't seem possible. If I define this:
function foo<T>(fun: (t: T) => T): (t: T) => T {
return fun
}
And then I do this
var bar = foo(a => a)
Then the type of bar
will be (t: unknown) => unknown
instead of (a: T) => T
. So I guess I just hit another dead end? If so, is there any other alternative?