I am creating a series of class which all extend an abstract class Operation<T, R>, on which is defined the method perform(t: T): R.
I want a conditional operation ConditionalOperation<T, R> extends Operation<T, T | R>, with a constructor which signature would be similar to this one:
constructor(
private predicate: (value: T) => boolean,
private thenOp: Operation<T, R>,
private elseOp?: Operation<T, R>
) {
super();
}
The method perform of this operation would work as follows:
- If
elseOpis defined, the input value is passed tothenOpif the predicate is true, else it is passed toelseOp. - If
elseOpis undefined, the input value is passed tothenOpif the predicate is true, else the value is returned directly.
This behaviour is the reason for the T | R in the signature of ConditionalOperation: if the elseOp is defined, the return type is R, otherwise it's T | R.
Now, I would like to try and see if there is a way to typecheck the ConditionalOperation to have a return type that depends on whether elseOp is defined. I tried the following:
abstract class Operation<T, R> {
abstract perform(t: T): R;
}
type ConditionalReturnType<T, R, EOp extends Operation<T, R>> = undefined extends EOp ? T | R : R;
class ConditionalOperation<T, R, EOp extends Operation<T, R>> extends Operation<T, ConditionalReturnType<T, R, EOp>> {
constructor(
private predicate: (value: T) => boolean,
private thenOp: Operation<T, R>,
private elseOp?: EOp
) {
super();
}
perform(t: T): ConditionalReturnType<T, R, EOp> {
if (this.predicate(t)) {
return this.thenOp.perform(t);
} else if (typeof this.elseOp !== 'undefined') {
return this.elseOp.perform(t);
} else {
return t; // Type 'T' is not assignable to type 'ConditionalReturnType<T, R, EOp>'.
}
}
}
Typescript does not seem to allow returning t here, even though we know at this point that elseOp is undefined, which should be possible if and only if ConditionalReturnType<T, R, EOp> is T | R.
Is there another way of doing this, aside from creating two conditional classes?