@hexadrop/either
Version:
Hexagonal architecture utils library
252 lines (232 loc) • 9.41 kB
text/typescript
import type { EitherValue } from './either.types';
/**
* The `Either` class represents a value of one of two possible types (a disjoint union).
* An instance of `Either` is either an instance of `Left` or `Right`.
*
* `Either` is often used as an alternative to `Option` for dealing with possible missing values.
* In this usage, `Nothing` is replaced with a `Left` which can contain useful information.
* `Right` takes the place of `Just` and represents the result of computations that do not fail.
*
* @template L The type of the left value.
* @template R The type of the right value.
*/
export default class Either<L, R> {
private constructor(private readonly value: EitherValue<L, R>) {}
/**
* Creates a new `Either` instance representing a left value.
*
* @static
* @returns {Either<L, R>} A new `Either` instance encapsulating the left value.
* @template L The type of the left value.
* @template R The type of the right value.
*/
static left<R>(): Either<never, R>;
/**
* Creates a new `Either` instance representing a left value.
*
* @static
* @param {L} value The left value.
* @returns {Either<L, R>} A new `Either` instance encapsulating the left value.
* @template L The type of the left value.
* @template R The type of the right value.
*/
static left<L, R>(value: L): Either<L, R>;
static left<L, R>(value?: L): Either<L, R> | Either<never, R> {
if (value || value === 0) {
return new Either<L, R>({ kind: 'left', leftValue: value });
}
return new Either<never, R>({ kind: 'left', leftValue: undefined as never });
}
/**
* Creates a new `Either` instance representing a right value.
*
* @static
* @returns {Either<L, R>} A new `Either` instance encapsulating the right value.
* @template L The type of the left value.
* @template R The type of the right value.
*/
static right<L>(): Either<L, never>;
/**
* Creates a new `Either` instance representing a right value.
*
* @static
* @param {R} value The right value.
* @returns {Either<L, R>} A new `Either` instance encapsulating the right value.
* @template L The type of the left value.
* @template R The type of the right value.
*/
static right<L, R>(value: R): Either<L, R>;
static right<L, R>(value?: R): Either<L, never> | Either<L, R> {
if (value || value === 0) {
return new Either<L, R>({ kind: 'right', rightValue: value });
}
return new Either<L, never>({ kind: 'right', rightValue: undefined as never });
}
/**
* Transforms the left value of the `Either` instance by applying a provided function and returns a new `Either` instance.
* If the `Either` instance represents a right value, the original `Either` instance is returned.
*
* @param {(left: L) => Either<T, R>} leftFunction The function to apply to the left value.
* @returns {Either<T, R>} A new `Either` instance with the transformed left value and the original right value, or the original `Either` instance if it represents a right value.
* @template L - The type of the 'left' value.
* @template R - The type of the 'right' value.
* @template T The type of the transformed right value.
*/
flatMapLeft<T>(leftFunction: (left: L) => Either<T, R>): Either<T, R> {
return this.fold(
leftValue => leftFunction(leftValue),
rightValue => Either.right(rightValue)
);
}
/**
* Transforms the right value of the `Either` instance by applying a provided function and returns a new `Either` instance.
* If the `Either` instance represents a left value, the original `Either` instance is returned.
*
* @param {(right: R) => Either<L, T>} rightFunction The function to apply to the right value.
* @returns {Either<L, T>} A new `Either` instance with the original left value and the transformed right value, or the original `Either` instance if it represents a left value.
* @template L - The type of the 'left' value.
* @template R - The type of the 'right' value.
* @template T The type of the transformed right value.
*/
flatMapRight<T>(rightFunction: (right: R) => Either<L, T>): Either<L, T> {
return this.fold(
leftValue => Either.left(leftValue),
rightValue => rightFunction(rightValue)
);
}
/**
* Applies a function to the encapsulated value and returns the result.
* The function to apply is determined by the kind of the value.
*
* @param {(left: L) => T} leftFunction The function to apply if the value is of kind 'left'.
* @param {(right: R) => T} rightFunction The function to apply if the value is of kind 'right'.
* @returns {T} The result of applying the appropriate function to the encapsulated value.
* @template L - The type of the 'left' value.
* @template R - The type of the 'right' value.
* @template T The type of the result.
*/
fold<T>(leftFunction: (left: L) => T, rightFunction: (right: R) => T): T {
switch (this.value.kind) {
case 'left': {
return leftFunction(this.value.leftValue);
}
case 'right': {
return rightFunction(this.value.rightValue);
}
}
}
/**
* Returns the left value if the `Either` instance represents a left value.
* Throws an error if the `Either` instance represents a right value.
*
* @param {string} [errorMessage] The error message to throw if the `Either` instance represents a right value.
* If not provided, a default error message is used.
* @returns {L} The left value.
* @throws {Error} If the `Either` instance represents a right value.
* @template L - The type of the 'left' value.
*/
getLeft(errorMessage?: string): L {
const throwFunction = () => {
if (errorMessage) {
throw new Error(errorMessage);
}
throw new Error(`The value is right: ${JSON.stringify(this.value)}`);
};
return this.fold(
leftValue => leftValue,
() => throwFunction()
);
}
/**
* Returns the left value if the `Either` instance represents a left value.
* Returns the provided default value if the `Either` instance represents a right value.
*
* @param {L} defaultValue The default value to return if the `Either` instance represents a right value.
* @returns {L} The left value or the default value.
* @template L - The type of the 'left' value.
*/
getLeftOrElse(defaultValue: L): L {
return this.fold(
someValue => someValue,
() => defaultValue
);
}
/**
* Returns the right value if the `Either` instance represents a right value.
* Throws an error if the `Either` instance represents a left value.
*
* @param {string} [errorMessage] The error message to throw if the `Either` instance represents a left value.
* If not provided, a default error message is used.
* @returns {R} The right value.
* @throws {Error} If the `Either` instance represents a left value.
* @template R - The type of the 'right' value.
*/
getRight(errorMessage?: string): R {
const throwFunction = () => {
if (errorMessage) {
throw new Error(errorMessage);
}
throw new Error(`The value is left: ${JSON.stringify(this.value)}`);
};
return this.fold(
() => throwFunction(),
rightValue => rightValue
);
}
/**
* Returns the right value if the `Either` instance represents a right value.
* Returns the provided default value if the `Either` instance represents a left value.
*
* @param {R} defaultValue The default value to return if the `Either` instance represents a left value.
* @returns {R} The right value or the default value.
* @template R - The type of the 'right' value.
*/
getRightOrElse(defaultValue: R): R {
return this.fold(
() => defaultValue,
someValue => someValue
);
}
/**
* Checks if the `Either` instance represents a left value.
*
* @returns {boolean} `true` if the `Either` instance represents a left value, `false` otherwise.
*/
isLeft(): boolean {
return this.value.kind === 'left';
}
/**
* Checks if the `Either` instance represents a right value.
*
* @returns {boolean} `true` if the `Either` instance represents a right value, `false` otherwise.
*/
isRight(): boolean {
return this.value.kind === 'right';
}
/**
* Transforms the left value of the `Either` instance by applying a provided function.
* Returns a new `Either` instance with the transformed left value and the original right value.
*
* @param {(l: L) => T} leftFunction The function to apply to the left value.
* @returns {Either<T, R>} A new `Either` instance with the transformed left value and the original right value.
* @template L - The type of the 'left' value.
* @template R - The type of the 'right' value.
* @template T The type of the transformed left value.
*/
mapLeft<T>(leftFunction: (l: L) => T): Either<T, R> {
return this.flatMapLeft(l => Either.left(leftFunction(l)));
}
/**
* Transforms the right value of the `Either` instance by applying a provided function.
* Returns a new `Either` instance with the original left value and the transformed right value.
*
* @param {(r: R) => T} rightFunction The function to apply to the right value.
* @returns {Either<L, T>} A new `Either` instance with the original left value and the transformed right value.
* @template L - The type of the 'left' value.
* @template R - The type of the 'right' value.
* @template T The type of the transformed right value.
*/
mapRight<T>(rightFunction: (r: R) => T): Either<L, T> {
return this.flatMapRight(r => Either.right(rightFunction(r)));
}
}