I am trying to type an implementation of Either and I struggle to make the two variants play well with each other.
First, the implementation:
type Unary<X, R> = (x: X) => R;
type Either<L=unknown, R=unknown> = Left<L> | Right<R>
class Right<R=unknown> {
constructor (private value: R) {}
static of <T>(value:T):Right<T> { return new Right(value); }
map <U>(f:Unary<R, U>):Right<U> {
return Right.of(f(this.value));
}
ap <T>(either: Left<T>): Left<T>
ap <U>(either: Right<Unary<R, U>>): Right<U>
ap <T, U>(either: Either<T, Unary<R, U>>): Either<T, U> {
return either instanceof Left
? either
: this.map(either.value);
}
}
class Left<L=unknown> {
constructor (private value: L) {}
static of <T>(value:T):Left<T> { return new Left(value); }
map (_:Function): Left<L> { return this; }
ap <T>(either: Left<T>): Left<T>
ap (either: Right): Left<L>
ap (either: Either): Left {
return either instanceof Left ? either : this;
}
}
On the surface, everything appears to be working fine both at runtime and compile time:
const r1 = Right.of(1);
const r2 = Right.of(2);
const l1 = Left.of(Error('foo'));
const l2 = Left.of(Error('bar'));
const add = (a:number) => (b:number) => a + b
// Right { value: 3 } : Right<number>
const rr = r2.ap(r1.map(add));
// Left { value: [Error: foo] } : Left<Error>
const lr = r2.ap(l1.map(add));
// Left { value: [Error: bar] } : Left<Error>
const rl = l2.ap(r1.map(add));
// Left { value: [Error: foo] } : Left<Error>
const ll = l2.ap(l1.map(add));
...but it falls apart if I try to define a lift function.
const lift2 = <A, B, R>(f:(a: A) => (b: B) => R) => {
function lifted(a:Right<A>, b:Right<B>): Right<R>
function lifted<U>(a:Right<A>, b:Left<U>): Left<U>
function lifted<T>(a:Left<T>, b:Right<B>): Left<T>
function lifted<T>(a:Left<T>, b:Left): Left<T>
function lifted(a: Either, b: Either):Either {
return b.ap(a.map(f));
// ------=-
}
return lifted;
}
I have 2 distinct errors
Argument of type 'Left<unknown> | Right<(b: B) => R>' is not assignable to parameter of type 'Left<unknown>'.
Type 'Right<(b: B) => R>' is not assignable to type 'Left<unknown>'.
Types have separate declarations of a private property 'value
The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
Argument of type '(a: A) => (b: B) => R' is not assignable to parameter of type 'Unary<unknown, (b: B) => R> & Function'.
Type '(a: A) => (b: B) => R' is not assignable to type 'Unary<unknown, (b: B) => R>'.
Types of parameters 'a' and 'x' are incompatible.
Type 'unknown' is not assignable to type 'A'.
'A' could be instantiated with an arbitrary type which could be unrelated to 'unknown'
Oddly enough, the correct types are inferred on the call site:
const add = lift2(
(a:number) => (b:number) => a + b
);
// Right { value: 3 } : Right<number>
const rr = add(r1, r2);
// Left { value: [Error: foo] } : Left<Error>
const lr = add(l1, r2);
// Left { value: [Error: bar] } : Left<Error>
const rl = add(r1, l2);
// Left { value: [Error: foo] } : Left<Error>
const ll = add(l1, l2);
- I don't know why
Left<unknown>
is inferred for the type parameter - I don't know where the
unknown
inUnary<unknown, (b: B) => R>
comes from