I'm currently trying to wrap my head around a more complicated type inference problem.
Suppose I have a generic base type Monad<R> (a monad encapsulating a value of type R) and a derived type Either<L,R> with extends it.
interface Monad<T> {
unit<V>(value: V): Monad<V>;
map<V>(f: (x: T) => V): Monad<V>;
unsafeLift(): T;
}
class Either<L, R> implements Monad<R> {
private _left?: L;
private _right?: R;
constructor(leftVal: L | undefined, rightVal: R | undefined) {
if (leftVal !== undefined && rightVal !== undefined) {
throw new Error("Can only set left or right to a defined value!");
}
this._left = leftVal;
this._right = rightVal;
}
get left(): L {
if (this._left) {
return this._left!;
}
throw new Error("No left value");
}
get right(): R {
if (this._right) {
return this._right!;
}
throw new Error("No right value");
}
public map<V>(f: (x: R) => V): Either<L, V> {
if (this._left) { return new Either<L, V>(this.left, undefined); }
return new Either<L, V>(undefined, f(this.right));
}
public unit<V>(x: V) {
return new Either<L, V>(undefined, x);
}
public unsafeLift = () => {
if (this._right) { return this._right!; }
throw new Error("Is not a right value");
}
}
I now want to infer the correct return type for a map-style function
map<U, V>((x: U) => V, Monad<U>): Monad<V>;
Using it as it is will always return the base type (as it should be when implemented that way), even if I provide a Either<L,R> as an argument.
I want to adapt this in a way that if the second argument to the function is of a type M<U> (with M extending Monad), map should return some M<V>.
For example, the following code fails:
function map<T, U, L extends Monad<T>>(f: (x: T) => U, monad: L)
{
return monad.map(f);
}
const either1 = new Either<unknown, number>(undefined, 10);
const mapped: Either<string, number> = map((x: number) => x * 2,
either1); // Type error, return value of map is Monad<number>, not Either<unknown, string>
It's easy to infer the inner type (e.g. type InnerType = M extends Monad<U> ? U : never), but I don't need that. Is there even a way to infer the outer type of that generic?