Skip to content

Instantly share code, notes, and snippets.

@mdubourg001
Last active February 12, 2020 10:23
Show Gist options
  • Save mdubourg001/143ed0de4e85108c5c576b38ccc754ec to your computer and use it in GitHub Desktop.
Save mdubourg001/143ed0de4e85108c5c576b38ccc754ec to your computer and use it in GitHub Desktop.
Typescript Either Monad Implementation
abstract class Monad<T> {
protected value: T;
public abstract map(f: Function): Monad<T>;
public abstract flatMap(f: Function): Monad<T>;
}
enum EitherType {
Left = "Left",
Right = "Right"
}
class Either<T> extends Monad<T> {
private t: EitherType;
constructor(val: T, t: EitherType = EitherType.Right) {
super();
this.value = val;
this.t = t;
}
static of<T>(val: T): Either<T> {
return new Either(val);
}
static Left<T>(val: T): Either<T> {
return new Either(val, EitherType.Left);
}
static Right<T>(val: T): Either<T> {
return new Either(val);
}
map<B>(f: (val: T) => T | B): Either<T | B> {
return this.t === EitherType.Left ? this : Either.of(f(this.value));
}
flatMap<B>(f: (val: T) => Either<T | B>): Either<T | B> {
return this.t === EitherType.Left ? this : f(this.value);
}
either<B>(lmap: (val: T) => B, rmap: (val: T) => B): B {
return this.t === EitherType.Left ? lmap(this.value) : rmap(this.value);
}
isLeft(): boolean {
return this.t === EitherType.Left;
}
isRight(): boolean {
return !this.isLeft();
}
private isSameTAs(m: Either<T>): boolean {
return (this.isLeft() && m.isLeft()) || (this.isRight() && m.isRight());
}
private isSameValueAs(val: T): boolean {
return this.value === val;
}
equals(m: Either<T>): boolean {
return (
this.isSameTAs(m) && m.either(this.isSameValueAs, this.isSameValueAs)
);
}
toString(): string {
return `${this.t} ${this.value}`;
}
}
// -----
// utils
// -----
const log = (...args: any[]) => console.log(`=> ${args.join(' ')}`);
const error = (...args: any[]) => console.log(`=> ERROR ${args.join(' ')}`);
/**
* calls and returns the result of either 'iftrue' or 'iffalse' functions
* based on the result of the pred function evalutation
*/
const cond = (
pred: (val: any) => boolean,
iftrue: (val: any) => any,
iffalse: (val: any) => any,
) => (val: any) => (pred(val) ? iftrue(val) : iffalse(val));
const isNumber = (x: any): boolean => typeof x === 'number';
// -----
// examples
// -----
const { Left, Right } = Either;
let e = new Either(42);
log(e); // => Right 42
e = Left(42);
log(e); // => Left 42
const double = (x: number) => x * 2;
const eitherDouble = (x: number) => Either.of(x * 2);
const right = Right(10).map(double);
log(right); // => Right 20
const left = Left(10).map(double);
log(left); // => Left 10
right.either(error, log); // => 20
left.either(error, log); // => ERROR 10
const getDataOfUnknownType = (): unknown => {
const r: number = Math.random();
switch (true) {
case r <= 0.33:
return 42;
case r > 0.33 && r <= 0.66:
return 'Hello World!';
case r > 0.66:
return true;
}
};
const data = cond(isNumber, Right, Left)(getDataOfUnknownType());
log(data); // => Right 42 | Left "Hello World" | Left true
data
.map(double)
.flatMap(eitherDouble)
.map(double)
.either(
(x: any) => error(`Value of type '${typeof x}' isn't accepted.`),
log, // => 336
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment