/** * https://github.com/gigobyte/purify * Copyright (c) 2018, Stanislav Iliev. Licensed under MIT. */ import { Maybe, Just, Nothing } from './Maybe.js' export type EitherPatterns = | { Left: (l: L) => T; Right: (r: R) => T } | { _: () => T } export interface Either { /** Returns true if `this` is `Left`, otherwise it returns false */ isLeft(): this is Either /** Returns true if `this` is `Right`, otherwise it returns false */ isRight(): this is Either toJSON(): L | R inspect(): string toString(): string /** Given two functions, maps the value inside `this` using the first if `this` is `Left` or using the second one if `this` is `Right`. * If both functions return the same type consider using `Either#either` instead */ bimap(f: (value: L) => L2, g: (value: R) => R2): Either /** Maps the `Right` value of `this`, acts like an identity if `this` is `Left` */ map(f: (value: R) => R2): Either /** Maps the `Left` value of `this`, acts like an identity if `this` is `Right` */ mapLeft(f: (value: L) => L2): Either /** Applies a `Right` function over a `Right` value. Returns `Left` if either `this` or the function are `Left` */ ap(other: Either R2>): Either /** Compares `this` to another `Either`, returns false if the constructors or the values inside are different, e.g. `Right(5).equals(Left(5))` is false */ equals(other: Either): boolean /** Transforms `this` with a function that returns an `Either`. Useful for chaining many computations that may fail */ chain(f: (value: R) => Either): Either /** The same as Either#chain but executes the transformation function only if the value is Left. Useful for recovering from errors */ chainLeft(f: (value: L) => Either): Either /** Flattens nested Eithers. `e.join()` is equivalent to `e.chain(x => x)` */ join(this: Either>): Either /** Returns the first `Right` between `this` and another `Either` or the `Left` in the argument if both `this` and the argument are `Left` */ alt(other: Either): Either /** Lazy version of `alt` */ altLazy(other: () => Either): Either /** Takes a reducer and an initial value and returns the initial value if `this` is `Left` or the result of applying the function to the initial value and the value inside `this` */ reduce(reducer: (accumulator: T, value: R) => T, initialValue: T): T /** Returns `this` if it's a `Left`, otherwise it returns the result of applying the function argument to `this` and wrapping it in a `Right` */ extend(f: (value: Either) => R2): Either /** Returns the value inside `this` if it's a `Right` or either throws the value or a generic exception depending on whether the value is an Error */ unsafeCoerce(): R /** Structural pattern matching for `Either` in the form of a function */ caseOf(patterns: EitherPatterns): T /** Returns the value inside `this` if it\'s `Left` or a default value if `this` is `Right` */ leftOrDefault(defaultValue: L): L /** Returns the value inside `this` if it\'s `Right` or a default value if `this` is `Left` */ orDefault(defaultValue: R): R /** Lazy version of `orDefault`. Takes a function that returns the default value, that function will be called only if `this` is `Left` */ orDefaultLazy(getDefaultValue: () => R): R /** Lazy version of `leftOrDefault`. Takes a function that returns the default value, that function will be called only if `this` is `Right` */ leftOrDefaultLazy(getDefaultValue: () => L): L /** Runs an effect if `this` is `Left`, returns `this` to make chaining other methods possible */ ifLeft(effect: (value: L) => any): this /** Runs an effect if `this` is `Right`, returns `this` to make chaining other methods possible */ ifRight(effect: (value: R) => any): this /** Constructs a `Just` with the value of `this` if it\'s `Right` or a `Nothing` if `this` is `Left` */ toMaybe(): Maybe /** Constructs a `Just` with the value of `this` if it\'s `Left` or a `Nothing` if `this` is `Right` */ leftToMaybe(): Maybe /** Extracts the value out of `this` */ extract(): L | R /** Returns `Right` if `this` is `Left` and vice versa */ swap(): Either 'fantasy-land/bimap'( f: (value: L) => L2, g: (value: R) => R2 ): Either 'fantasy-land/map'(f: (value: R) => R2): Either 'fantasy-land/ap'(other: Either R2>): Either 'fantasy-land/equals'(other: Either): boolean 'fantasy-land/chain'( f: (value: R) => Either ): Either 'fantasy-land/alt'(other: Either): Either 'fantasy-land/reduce'( reducer: (accumulator: T, value: R) => T, initialValue: T ): T 'fantasy-land/extend'(f: (value: Either) => R2): Either } interface EitherTypeRef { /** Takes a value and wraps it in a `Right` */ of(value: R): Either /** Takes a list of `Either`s and returns a list of all `Left` values */ lefts(list: Either[]): L[] /** Takes a list of `Either`s and returns a list of all `Right` values */ rights(list: Either[]): R[] /** Calls a function and returns a `Right` with the return value or an exception wrapped in a `Left` in case of failure */ encase(throwsF: () => R): Either /** Turns a list of `Either`s into an `Either` of list */ sequence(eithers: Either[]): Either isEither(x: unknown): x is Either 'fantasy-land/of'(value: R): Either } export const Either: EitherTypeRef = { of(value: R): Either { return right(value) }, lefts(list: Either[]): L[] { let result = [] for (const x of list) { if (x.isLeft()) { result.push(x.extract()) } } return result }, rights(list: Either[]): R[] { let result = [] for (const x of list) { if (x.isRight()) { result.push(x.extract()) } } return result }, encase(throwsF: () => R): Either { try { return right(throwsF()) } catch (e) { return left(e as L); } }, sequence(eithers: Either[]): Either { let res: R[] = [] for (const e of eithers) { if (e.isLeft()) { return e } res.push(e.extract() as R) } return right(res) }, isEither(x: unknown): x is Either { return x instanceof Left || x instanceof Right }, 'fantasy-land/of'(value: R): Either { return Either.of(value) } } class Right implements Either { private _ = 'R' constructor(private __value: R) { } isLeft(): false { return false } isRight(): true { return true } toJSON(): R { return this.__value } inspect(): string { return `Right(${JSON.stringify(this.__value)})` } toString(): string { return this.inspect() } bimap(_: (value: L) => L2, g: (value: R) => R2): Either { return right(g(this.__value)) } map(f: (value: R) => R2): Either { return right(f(this.__value)) } mapLeft(_: (value: L) => L2): Either { return this as any } ap(other: Either R2>): Either { return other.isRight() ? this.map(other.extract()) : (other as any) } equals(other: Either): boolean { return other.isRight() ? this.__value === other.extract() : false } chain(f: (value: R) => Either): Either { return f(this.__value) } chainLeft(_: (value: L) => Either): Either { return this as any } join(this: Right, L>): Either { return this.__value as any } alt(_: Either): Either { return this } altLazy(_: () => Either): Either { return this } reduce(reducer: (accumulator: T, value: R) => T, initialValue: T): T { return reducer(initialValue, this.__value) } extend(f: (value: Either) => R2): Either { return right(f(this)) } unsafeCoerce(): R { return this.__value } caseOf(patterns: EitherPatterns): T { return '_' in patterns ? patterns._() : patterns.Right(this.__value) } leftOrDefault(defaultValue: L): L { return defaultValue } orDefault(_: R): R { return this.__value } orDefaultLazy(_: () => R): R { return this.__value } leftOrDefaultLazy(getDefaultValue: () => L): L { return getDefaultValue() } ifLeft(_: (value: L) => any): this { return this } ifRight(effect: (value: R) => any): this { return effect(this.__value), this } toMaybe(): Maybe { return Just(this.__value) } leftToMaybe(): Maybe { return Nothing } extract(): L | R { return this.__value } swap(): Either { return left(this.__value) } 'fantasy-land/bimap' = this.bimap 'fantasy-land/map' = this.map 'fantasy-land/ap' = this.ap 'fantasy-land/equals' = this.equals 'fantasy-land/chain' = this.chain 'fantasy-land/alt' = this.alt 'fantasy-land/reduce' = this.reduce 'fantasy-land/extend' = this.extend } Right.prototype.constructor = Either as any class Left implements Either { private _ = 'L' constructor(private __value: L) { } isLeft(): true { return true } isRight(): false { return false } toJSON(): L { return this.__value } inspect(): string { return `Left(${JSON.stringify(this.__value)})` } toString(): string { return this.inspect() } bimap(f: (value: L) => L2, _: (value: R) => R2): Either { return left(f(this.__value)) } map(_: (value: R) => R2): Either { return this as any } mapLeft(f: (value: L) => L2): Either { return left(f(this.__value)) } ap(other: Either R2>): Either { return other.isLeft() ? other : (this as any) } equals(other: Either): boolean { return other.isLeft() ? other.extract() === this.__value : false } chain(_: (value: R) => Either): Either { return this as any } chainLeft(f: (value: L) => Either): Either { return f(this.__value) } join(this: Either>): Either { return this as any } alt(other: Either): Either { return other } altLazy(other: () => Either): Either { return other() } reduce(_: (accumulator: T, value: R) => T, initialValue: T): T { return initialValue } extend(_: (value: Either) => R2): Either { return this as any } unsafeCoerce(): never { if (this.__value instanceof Error) { throw this.__value } throw new Error('Either#unsafeCoerce was ran on a Left') } caseOf(patterns: EitherPatterns): T { return '_' in patterns ? patterns._() : patterns.Left(this.__value) } leftOrDefault(_: L): L { return this.__value } orDefault(defaultValue: R): R { return defaultValue } orDefaultLazy(getDefaultValue: () => R): R { return getDefaultValue() } leftOrDefaultLazy(_: () => L): L { return this.__value } ifLeft(effect: (value: L) => any): this { return effect(this.__value), this } ifRight(_: (value: R) => any): this { return this } toMaybe(): Maybe { return Nothing } leftToMaybe(): Maybe { return Just(this.__value) } extract(): L | R { return this.__value } swap(): Either { return right(this.__value) } 'fantasy-land/bimap' = this.bimap 'fantasy-land/map' = this.map 'fantasy-land/ap' = this.ap 'fantasy-land/equals' = this.equals 'fantasy-land/chain' = this.chain 'fantasy-land/alt' = this.alt 'fantasy-land/reduce' = this.reduce 'fantasy-land/extend' = this.extend } Left.prototype.constructor = Either as any const left = (value: L): Either => new Left(value) const right = (value: R): Either => new Right(value) export { left as Left, right as Right }