Closed kungfooman closed 8 months ago
> I still wonder what else is the point of the cls method?
If you look at the commit history you will see that all of the pqr.ts
stuff used to be sprinkled about index.ts
behind various if
statements. The cls
shenanigans get around TypeScipt's lack of abstract static methods. cls()
is a lot cleaner than the previous if
soup, but maybe not as clean as it could be...
So thank you for the tip! I'm still very new to this TypeScript stuff I will look into cleaner ways to implement the overrides.
Unfortunately I can't make your interface trick work with a more complex toy example. (I'll try turning cls
into a getter though.)
interface ConstructorOf<T> {
new(): T;
}
class Animal {
age: number
constructor(age: number ) {
this.age = age;
}
get cls(): ConstructorOf<this> {
return this.constructor as any;
}
clone() {
const ret = new this.cls(this.age); // Doesn't know about what the number of arguments should be
return ret;
}
breed() {
return this.cls.baby(); // Doesn't know that there's a static method
}
static baby() {
return new Animal(0);
}
}
Thank you for implementing the getter suggestion right away, for the rest of the issues there are also solutions:
age: number
constructor(age: number ) {
this.age = age;
}
Just a note, you can shortcut this into:
constructor(public age: number ) {
// Nothing yet.
}
This is hard-coded into Animal
, which causes problems for extending the class:
static baby() {
return new Animal(0);
}
To make it work properly in extending classes, this should be:
static baby() {
return new this(0);
}
To fix the static baby
error, some type programming needs to be done:
console.clear();
export type AnimalConstructor<C> = {
new (...args: ConstructorParameters<typeof Animal>): C;
} & {
[Q in keyof typeof Animal]: typeof Animal[Q]
};
class Animal {
constructor(public age: number) {
// Nothing yet.
}
get cls(): AnimalConstructor<this> {
return this.constructor as any;
}
clone() {
const ret = new this.cls(this.age);
return ret;
}
breed() {
return this.cls.baby();
}
static baby() {
return new this(0);
}
}
class Mammal extends Animal {
// Nothing yet.
}
const unittests = [
() => new Animal(10).age == 10,
() => new Mammal(20).age == 20,
() => new Animal(10).clone().age == 10,
() => new Mammal(20).clone().age == 20,
() => new Animal(10).clone() instanceof Animal,
() => new Mammal(20).clone() instanceof Mammal,
() => Animal.baby() instanceof Animal,
() => Mammal.baby() instanceof Mammal,
];
console.table(
unittests.map((_,i) => ({
test: _.toString().slice(6),
result: _()
}))
);
The generic type is not exactly "nice and easy" anymore though and this
doesn't work the way I would wish it to, the generic type needs to fall back onto typeof Animal
all the time 😞
Made a little bit less hacky but still hacky.
Too bad TS can't even infer the type of this.constructor
, but n1
I looked a bit through the code and wondered how class inheritance for proper OOP is implemented and I found this e.g.:
https://github.com/frostburn/ts-geometric-algebra/blob/bba5389f3ba0ef66295ce5b2b21592c343e4be55/src/pqr.ts#L87-L91
It looks like the only job of the
cls
method is to returnthis.constructor
and if the base class had that method, you wouldn't need to overridecls
in every other class.This is: 1) adding extra complexity for whoever wants to extend a class 2) adding bracket salad like
new (this.cls())()
cls
can just be a getter to remove the extranew (...())
and through inheritance it will always point to its own class:TS PlayGround: https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgMIHsQGcxQK4JjpQDyMAPACoB8yA3gLABQyyIEA7gBQCUAXMkoBuZgF9mCADZwsWZAEEQwALZxJ9ZqwDmEMMilZeAjNlwEipCmAAWwLLUYtWyKLrxQQyG3YB0CTDj4hMTIMqEgAJ4iTuJOUpgQvPSsms7+pi66yAC8bJxetlh+kljRzplg7p6uYGXIsbFSMnIAsnDKquoQAB6QIAAmcooqahpOAPTjyABy6N4gWsgRuj5iEgF6cEqdAIw5eRwK22rR6TjhI5IATPtblzvFCbynG8iqHWp7ueyHbR+SLwy706N1ywM+j3Yz2YQA
I didn't test this library yet, but I still wonder what else is the point of the
cls
method?