How to properly infer the type of higher order generics in Typescript

Viewed 67

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?

0 Answers
Related