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
elseOp
is defined, the input value is passed tothenOp
if the predicate is true, else it is passed toelseOp
. - If
elseOp
is undefined, the input value is passed tothenOp
if 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?