Skip to content

Instantly share code, notes, and snippets.

@sambacha
Created January 18, 2025 12:47
Show Gist options
  • Save sambacha/3f8b2c7c10430b41b5d4cd43c358da11 to your computer and use it in GitHub Desktop.
Save sambacha/3f8b2c7c10430b41b5d4cd43c358da11 to your computer and use it in GitHub Desktop.
TypeScript implementation of the Result monad pattern
// SPDX-License-Identifier: UPL-1.0 OR BSD-3
/**
* @file LibResult.ts
* @version 1.0.0
* @packageDocumentation
*
* A TypeScript implementation of the Result monad pattern, providing a type-safe way
* to handle operations that might fail. This pattern helps eliminate null checks
* and provides a more functional approach to error handling.
*
* @example
* ```typescript
* // Basic usage
* const divide = (a: number, b: number): Result<number> => {
* if (b === 0) return Result.err("Division by zero");
* return Result.ok(a / b);
* };
*
* const result = divide(10, 2)
* .map(result => result * 2)
* .fold(
* value => console.log(`Success: ${value}`),
* error => console.error(`Error: ${error}`)
* );
* ```
*/
/**
* Interface representing a successful result.
* @template T - The type of the successful result.
* @since 1.0.0
* @category Interfaces
*/
export interface IResultOk<T> {
res: T;
err?: undefined;
}
/**
* Interface representing a failed result.
* @template E - The type of the error.
* @since 1.0.0
* @category Interfaces
*/
export interface IResultErr<E = string> {
res?: undefined;
err: E;
}
/**
* Type alias for a result, which can either be successful or failed.
* @template T - The type of the successful result.
* @template E - The type of the error.
* @since 1.0.0
* @category Types
*/
export type TResult<T, E = string> = IResultOk<T> | IResultErr<E>;
/**
* Class for encapsulating a result, which can either be a success or failure.
* Provides utility methods for handling result values in a type-safe manner.
*
* This class implements the Result monad pattern, which is useful for:
* - Handling operations that might fail
* - Chaining multiple operations together
* - Providing type-safe error handling
*
* @template T - The type of the successful result.
* @template E - The type of the error.
* @since 1.0.0
* @category Classes
*
* @example
* ```typescript
* // Chaining multiple operations
* const result = await Result.ok(user)
* .andAsync(user => validateUser(user))
* .andAsync(user => saveToDatabase(user))
* .fold(
* user => ({ success: true, user }),
* error => ({ success: false, error })
* );
* ```
*/
export class Result<T, E = string> {
/** @private The underlying result value */
private readonly val: TResult<T, E>;
/**
* Creates a successful result.
* @param value - The value of the successful result.
* @returns A new Result instance containing the successful value.
* @throws Never
* @category Static Methods
*
* @example
* ```typescript
* const result = Result.ok(42);
* console.log(result.isSuccess()); // true
* ```
*/
public static ok<T>(value: T): Result<T> {
return new Result({ res: value });
}
/**
* Creates a failed result.
* @param error - The error message of the failed result.
* @returns A new Result instance containing the error.
* @throws Never
* @category Static Methods
*
* @example
* ```typescript
* const result = Result.err("Something went wrong");
* console.log(result.isFailure()); // true
* ```
*/
public static err<T, E = string>(error: E): Result<T, E> {
return new Result({ err: error });
}
/**
* Converts an existing result type into a `Result` instance.
* @param value - The existing result to convert.
* @returns A new Result instance wrapping the provided value.
* @throws Never
* @category Static Methods
*
* @example
* ```typescript
* const result: TResult<number> = { res: 42 };
* const wrapped = Result.from(result);
* ```
*/
public static from<T, E>(value: TResult<T, E>): Result<T, E> {
return new Result(value);
}
/**
* Constructs a `Result` instance.
* @param value - The underlying result value.
* @private
*/
constructor(value: TResult<T, E>) {
this.val = value;
}
/**
* Maps the successful result value to a new value using the provided function.
* @template U - The type of the new result value.
* @param fn - A function that transforms the successful result value.
* @example
* const result = Result.ok(10).map(x => x * 2); // Result.ok(20)
*/
public map<U>(fn: (value: T) => U): Result<U, E> {
return this.isOk(this.val)
? Result.ok(fn(this.val.res))
: Result.err(this.val.err);
}
/**
* Maps the successful result value to a new value asynchronously.
* @template U - The type of the new result value.
* @param fn - An asynchronous function that transforms the successful result value.
* @example
* const result = await Result.ok(10).mapAsync(async x => x * 2); // Result.ok(20)
*/
public async mapAsync<U>(fn: (value: T) => Promise<U>): Promise<Result<U, E>> {
return this.isOk(this.val)
? Result.ok(await fn(this.val.res))
: Result.err(this.val.err);
}
/**
* Chains the result to another result-producing function.
* @template U - The type of the new result value.
* @param fn - A function that takes the successful result value and returns a new `Result`.
* @example
* const result = Result.ok(10).and(x => Result.ok(x * 2)); // Result.ok(20)
*/
public and<U>(fn: (arg: T) => Result<U, E>): Result<U, E> {
return this.isOk(this.val)
? fn(this.val.res)
: Result.err(this.val.err);
}
/**
* Chains the result to another result-producing function asynchronously.
* @template U - The type of the new result value.
* @param fn - An asynchronous function that returns a new `Result`.
* @example
* const result = await Result.ok(10).andAsync(async x => Result.ok(x * 2)); // Result.ok(20)
*/
public async andAsync<U>(
fn: (arg: T) => Promise<Result<U, E>>
): Promise<Result<U, E>> {
return this.isOk(this.val)
? await fn(this.val.res)
: Result.err(this.val.err);
}
/**
* Provides an alternative result if the current result is a failure.
* @template U - The type of the alternative result value.
* @param fn - A function that takes the error message and returns an alternative `Result`.
* @example
* const result = Result.err("Error").orElse(err => Result.ok(42)); // Result.ok(42)
*/
public orElse<U>(fn: (error: E) => Result<U, E>): Result<T | U, E> {
return this.isOk(this.val)
? this
: fn(this.val.err);
}
/**
* Handles both success and error cases in a single function.
* @template U - The return type of the handling functions.
* @param onSuccess - A function that handles the success case.
* @param onError - A function that handles the error case.
* @example
* const result = Result.ok(42).fold(
* res => `Success: ${res}`,
* err => `Error: ${err}`
* ); // "Success: 42"
*/
public fold<U>(onSuccess: (value: T) => U, onError: (err: E) => U): U {
return this.isOk(this.val)
? onSuccess(this.val.res)
: onError(this.val.err);
}
/**
* Unwraps the successful result value or throws an error if the result is a failure.
* @throws An error if the result is a failure.
* @returns The successful result value.
* @example
* const value = Result.ok(42).unwrap(); // 42
*/
public unwrap(): T {
if (this.isOk(this.val)) {
return this.val.res;
}
throw new Error(this.val.err as string);
}
/**
* Checks if the result is a success.
* @returns `true` if the result is a success, otherwise `false`.
*/
public isSuccess(): boolean {
return this.isOk(this.val);
}
/**
* Checks if the result is a failure.
* @returns `true` if the result is a failure, otherwise `false`.
*/
public isFailure(): boolean {
return !this.isOk(this.val);
}
/**
* Retrieves the underlying value of the result.
* @returns The underlying result value.
*/
public toVal(): TResult<T, E> {
return this.val;
}
private isOk(val: TResult<T, E>): val is IResultOk<T> {
return !val.err;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment