Either
monad. The Either
monad is a type that can hold one of two possible values: a left value, which often represents an error or an exceptional case, and a right value, which represents a successful result. In TypeScript, implementing and using the Either
monad can significantly improve the robustness and readability of your code, especially when dealing with operations that might fail.The Either
monad is a type that can be in one of two states: Left
or Right
. By convention, the Left
state is used to represent an error or an exceptional situation, while the Right
state is used to represent a successful result.
Mathematically, an Either
type can be defined as a disjoint union of two types. For example, if we have types L
and R
, the Either<L, R>
type can hold a value of type L
(in the Left
case) or a value of type R
(in the Right
case), but not both at the same time.
The Either
monad comes with two main operations:
map
: It allows you to transform the Right
value inside the Either
monad without affecting the Left
value.chain
: It is used to compose operations that return Either
monads. If the current Either
is in the Left
state, the chain operation short - circuits and returns the Left
value; otherwise, it applies the provided function to the Right
value.// Define the base Either type
type Either<L, R> = Left<L> | Right<R>;
// Left class represents the error case
class Left<L> {
constructor(private readonly value: L) {}
isLeft(): this is Left<L> {
return true;
}
isRight(): this is Right<never> {
return false;
}
map<U>(_: (r: never) => U): Either<L, U> {
return this;
}
chain<U>(_: (r: never) => Either<L, U>): Either<L, U> {
return this;
}
getOrElse<U>(defaultValue: U): U {
return defaultValue;
}
}
// Right class represents the success case
class Right<R> {
constructor(private readonly value: R) {}
isLeft(): this is Left<never> {
return false;
}
isRight(): this is Right<R> {
return true;
}
map<U>(f: (r: R) => U): Either<never, U> {
return new Right(f(this.value));
}
chain<L, U>(f: (r: R) => Either<L, U>): Either<L, U> {
return f(this.value);
}
getOrElse<U>(_: U): R {
return this.value;
}
}
// Helper functions to create Either values
function left<L>(value: L): Either<L, never> {
return new Left(value);
}
function right<R>(value: R): Either<never, R> {
return new Right(value);
}
const result: Either<never, number> = right(5);
const mappedResult = result.map(x => x * 2);
if (mappedResult.isRight()) {
console.log(mappedResult.getOrElse(0)); // Output: 10
}
function divide(a: number, b: number): Either<string, number> {
if (b === 0) {
return left('Division by zero');
}
return right(a / b);
}
const divisionResult = right(10).chain(x => divide(x, 2));
if (divisionResult.isRight()) {
console.log(divisionResult.getOrElse(0)); // Output: 5
}
const divisionByZeroResult = right(10).chain(x => divide(x, 0));
if (divisionByZeroResult.isLeft()) {
console.log(divisionByZeroResult.getOrElse('No error')); // Output: Division by zero
}
Either
monad to handle errors in a more functional way. Instead of throwing exceptions, return a Left
value with an error message.chain
method. This way, if any operation fails, the entire chain short - circuits, and the error is propagated.Left
and Right
values have the correct types. This helps catch errors at compile - time.Either
monad immutable. Once created, an Either
value should not be modified. Instead, create new Either
values using methods like map
and chain
.Left
value, use descriptive error messages that provide useful information about what went wrong.The Either
monad in TypeScript is a powerful tool for handling errors and composing operations in a functional and predictable way. By separating the error and success cases into distinct states (Left
and Right
), it allows for more robust and readable code. Understanding the fundamental concepts, implementing the monad correctly, and following best practices can significantly improve the quality of your TypeScript applications.