import { Option, Some, None } from './Option' /** * Represents a value of one of two possible types (a disjoint union). * An instance of `Either` is either an instance of `Left` or `Right`. * By convention, `Left` holds errors and `Right` holds success values. * (as 'right' also means 'correct' in English) * * @template Left The type of the Left value. * @template Right The type of the Right value. */ export class Either { /** * @internal */ constructor(readonly left: Option, readonly right: Option) {} /** * Checks if this `Either` is a `Left`. */ isLeft(): boolean { return !this.left.isEmpty() } /** * Checks if this `Either` is a `Right`. */ isRight(): boolean { return !this.right.isEmpty() } /** * Gets the `Left` value. * @throws {Error} if this is a `Right`. */ getLeft(): Left { if (!this.isLeft()) throw new Error('Cannot getLeft on a Right') return this.left.get() } /** * Gets the `Right` value. * @throws {Error} if this is a `Left`. */ getRight(): Right { if (!this.isRight()) throw new Error('Cannot getRight on a Left') return this.right.get() } /** * Maps the `Right` value if this is a `Right`. * * @template NewRight The type of the new `Right` value. * @param fn The mapping function. */ map(fn: (right: Right) => NewRight): Either { if (this.isRight()) return new Either(None(), Some(fn(this.getRight()))) else return new Either(this.left, None()) } /** * Flat-maps the `Right` value if this is a `Right`. * * @template NewRight The type of the new `Right` value. * @param fn The mapping function returning an `Either`. */ flatMap(fn: (right: Right) => Either): Either { if (this.isRight()) return fn(this.getRight()) else return new Either(this.left, None()) } /** * Filters the `Right` value, returning `Left` if the predicate fails. * If the right exists and the predicate returns true, the right is returned unchanged. * If the right exists and the predicate returns false, the left is returned with the provided left value. * If the right does not exist, the left is returned unchanged. * @template NewLeft The type of the `Left` value if the filter fails. * @param fn The predicate function. * @param left The value to use for `Left` if the predicate fails. */ filterOrElse(fn: (right: Right) => boolean, left: NewLeft): Either { if (this.isRight()) { if (fn(this.getRight())) { return new Either(None(), this.right) } else { return new Either(Some(left), None()) } } else { return new Either(Some(left), None()) } } /** * Maps the `Left` value if this is a `Left`. * If the left exists, the mapping function is applied to the left value and the result is returned as a new `Left`. * If the left does not exist, the right is returned unchanged. * @template NewLeft The type of the new `Left` value. * @param fn The mapping function. */ mapLeft(fn: (left: Left) => NewLeft): Either { if (this.isLeft()) return new Either(Some(fn(this.getLeft())), None()) else return new Either(None(), this.right) } /** * Flat-maps the `Left` value if this is a `Left`. * If the left exists, the mapping function is applied to the left value and the result is returned as a new `Left`. * If the left does not exist, the right is returned unchanged. * @template NewLeft The type of the new `Left` value. * @param fn The mapping function returning an `Either`. */ flatMapLeft(fn: (left: Left) => Either): Either { if (this.isLeft()) return fn(this.getLeft()) else return new Either(None(), this.right) } /** * Filters the `Left` value, returning `Right` if the predicate fails. * If the left exists and the predicate returns true, the left is returned unchanged. * If the left exists and the predicate returns false, the right is returned with the provided right value. * If the left does not exist, the right is returned unchanged. * @template NewRight The type of the `Right` value if the filter fails. * @param fn The predicate function. * @param right The value to use for `Right` if the predicate fails. */ filterOrElseLeft(fn: (left: Left) => boolean, right: NewRight): Either { if (this.isLeft()) { if (fn(this.getLeft())) { return new Either(this.left, None()) } else { return new Either(None(), Some(right)) } } else { return new Either(None(), Some(right)) } } } /** * Creates a `Left` instance. * @template L Type of the `Left` value. * @template R Type of the `Right` value (defaults to `never`). */ export function Left(left: L): Either { return new Either(Some(left), None()) } /** * Creates a `Right` instance. * @template R Type of the `Right` value. * @template L Type of the `Left` value (defaults to `never`). */ export function Right(right: R): Either { return new Either(None(), Some(right)) }