Last active
February 12, 2020 10:23
-
-
Save mdubourg001/143ed0de4e85108c5c576b38ccc754ec to your computer and use it in GitHub Desktop.
Typescript Either Monad Implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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