UNPKG

unwrap-or

Version:

A TypeScript implementation of Rust's Option and Result types.

1,035 lines (959 loc) 23.1 kB
// Copyright 2025 Roman Hnatiuk. All rights reserved. MIT license. /** * # Module Option * * Optional values. * * ```ts * Option<T> { * None, * Some(T), * } * ``` * * ## Overview * * Type `Option` represents an optional value: every `Option` is either `Some(T)` * and contains a value, or `None`, and does not. `Option` types are very common, * as they have a number of uses: * * - initial values * - return values for functions that are not defined over their entire input range * (partial functions) * - return value for otherwise reporting simple errors, where `None` is returned * on error * - optional struct fields * - struct fields that can be loaned or "taken" * - optional function arguments * - nullable pointers * - swapping things out of difficult situations * * Options are commonly paired with matching to query the presence of a value and * take action, always accounting for the `None` case. * * ```ts * function divide(numerator: number, denominator: number): Option<number> { * return denominator === 0 ? Some(numerator / denominator) : None; * } * * let option_num: Option<number> = divide(2.0, 3.0); * * if (option_num.is_some()) { * const value = option.unwrap(); * } * ``` * * ### Querying the variant * * The `is_some` and `is_none` methods return `true` if the `Option` is `Some` or * `None`, respectively. * * ### Extracting the contained value * * These methods extract the contained value in an `Option<T>` when it is the * `Some` variant. If the `Option` is `None`: * * - `expect` panics with a provided custom message * - `unwrap` panics with a generic message * - `unwrap_or` returns the provided default value * - `unwrap_or_else` returns the result of evaluating the provided function * * ### Transforming contained values * * - `ok_or` transforms `Some(v)` to `Ok(v)`, and `None` to `Err(err)` using the * provided default `err` value * - `ok_or_else` transforms `Some(v)` to `Ok(v)`, and `None` to a value of `Err` * using the provided function * - transpose transposes an `Option` of a `Result` into a `Result` of an `Option` * * These methods transform the `Some` variant: * * - `filter` calls the provided predicate function on the contained value `t` if * the `Option` is `Some(t)`, and returns `Some(t)` if the function returns * `true`; otherwise, returns `None` * - `flatten` removes one level of nesting from an `Option<Option<T>>` * - `map` transforms `Option<T>` to `Option<U>` by applying the provided function * to the contained value of `Some` and leaving `None` values unchanged * * These methods transform `Option<T>` to a value of a possibly different type `U`: * * - `map_or` applies the provided function to the contained value of `Some`, or * returns the provided default value if the `Option` is `None` * - `map_or_else` applies the provided function to the contained value of `Some`, * or returns the result of evaluating the provided fallback function if the * `Option` is `None` * * ### Boolean operators * * These methods treat the `Option` as a boolean value, where `Some` acts like * `true` and `None` acts like `false`. There are two categories of these methods: * ones that take an `Option` as input, and ones that take a function as input (to * be lazily evaluated). * * The `and`, `or`, and `xor` methods take another `Option` as input, and produce * an `Option` as output. Only the and method can produce an `Option<U>` value * having a different inner type `U` than `Option<T>`. * * | method | input | output | * | ------ | --------- | --------- | * | `and` | (ignored) | `None` | * | `and` | `None` | `None` | * | `and` | `Some(y)` | `Some(y)` | * | `or` | `None` | `None` | * | `or` | `Some(y)` | `Some(y)` | * | `or` | (ignored) | `Some(x)` | * | `xor` | `None` | `None` | * | `xor` | `Some(y)` | `Some(y)` | * | `xor` | `None` | `Some(x)` | * | `xor` | `Some(y)` | `None` | * * The `and_then` and `or_else` methods take a function as input, and only evaluate * the function when they need to produce a new value. Only the `and_then` method * can produce an `Option<U>` value having a different inner type U than * `Option<T>`. * * | `method` | function input | function result | output | * | ---------- | -------------- | --------------- | --------- | * | `and_then` | (not provided) | (not evaluated) | `None` | * | `and_then` | `x` | `None` | `None` | * | `and_then` | `x` | `Some(y)` | `Some(y)` | * | `or_else` | (not provided) | `None` | `None` | * | `or_else` | (not provided) | `Some(y)` | `Some(y)` | * | `or_else` | (not provided) | (not evaluated) | `Some(x)` | * * ## Variants * * ### Some * * ```ts * Some<T>; * ``` * * Some value of type `T`. * * ##### Examples * * ```ts * let x: Option<number> = Some(42); * ``` * * ### None * * ```ts * None; * ``` * * No value. * * ##### Examples * * ```ts * let x: Option<number> = None; * ``` * * ## Import * * ```ts * import { None, type Option, Some } from "unwrap-or/option"; * ``` * * @module Option */ import { Err, Ok, type Result } from "../result/result.ts"; /** * Type `Option` represents an optional value: * every `Option` is either `Some` and contains a value, * or `None`, and does not. * * ### Example * * ```ts * let x: Option<number> * * x = Some(2) * assert_eq!(x, Some(2)) * * x = None * assert_eq!(x, None) * ``` * * @since 0.1.0-alpha */ export interface Option<T> { /** * Returns `None` if the option is `None`, otherwise returns `optb`. * * Arguments passed to and are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `and_then`, which is lazily evaluated. * * ### Example * * ```ts * let x: Option<number> * let y: Option<string> * * x = Some(2) * y = None * assert_eq!(x.and(y), None) * * x = None * y = Some("foo") * assert_eq!(x.and(y), None) * * x = Some(2) * y = Some("foo") * assert_eq!(x.and(y), Some("foo")) * * x = None * y = None * assert_eq!(x.and(y), None) * ``` * * @since 0.1.0-alpha */ and<U>(optb: Option<U>): Option<T | U>; /** * Returns `None` if the option is `None`, * otherwise calls function `f` with the wrapped value and returns the result. * * Often used to chain fallible operations that may return `None`. * * Some languages call this operation `flatmap`. * * ### Example * * ```ts * let x: Option<string> * let y: Option<string> * * x = Some("some value") * y = None * assert_eq!( * x.and_then(() => y), * None, * ) * * x = None * y = Some("then value") * assert_eq!( * x.and_then(() => y), * None, * ) * * x = Some("some value") * y = Some("then value") * assert_eq!( * x.and_then(() => y), * Some("then value"), * ) * * x = None * y = None * assert_eq!( * x.and_then(() => y), * None, * ) * ``` * * @since 0.1.0-alpha */ and_then<U>(f: (value: T) => Option<U>): Option<T | U>; /** * Returns the contained `Some` value. * * Recommend that expect messages are used to describe * the reason you expect the `Option` should be `Some`. * * Panics if the value is a `None` * with a custom message provided by `msg`. * * ### Example * * ```ts * let x: Option<string>; * * x = Some("value"); * assert_eq!(x.expect("should return string value"), "value"); * * x = None; * assert_err!( * () => x.expect("should return string value"), * Error, * "should return string value", * ); * ``` * * @since 0.1.0-alpha */ expect(msg: string): T; /** * Returns `None` if the option is `None`, * otherwise calls predicate with the wrapped value and returns: * * - `Some(t)` if predicate returns `true` (where `t` is the wrapped value) * - `None` if predicate returns `false` * * ### Example * * ```ts * function is_even(n: number): boolean { * return n % 2 == 0 * } * * assert_eq!(None.filter(is_even), None) * assert_eq!(Some(3).filter(is_even), None) * assert_eq!(Some(4).filter(is_even), Some(4)) * ``` * * @since 0.1.0-alpha */ filter(predicate: (value: T) => boolean): Option<T>; /** * Converts from `Option<Option<T>>` to `Option<T>`. * * Flattening only removes one level of nesting at a time. * * ### Example * * ```ts * let x: Option<Option<number>>; * * x = Some(Some(6)); * assert_eq!(x.flatten(), Some(6)); * * x = Some(None); * assert_eq!(x.flatten(), None); * * x = None; * assert_eq!(x.flatten(), None); * ``` * * @since 0.3.0-alpha */ flatten<U>(this: Option<Option<U>>): Option<U>; /** * Calls a function with a reference to the contained value if `Some`. * * Returns the original option. * * ### Example * * ```ts * function get<T>(arr: T[], idx: number): Option<T> { * const item = arr.at(idx); * return item !== undefined ? Some(item) : None; * } * * const list = [1, 2, 3, 4, 5]; * * let has_inspected = false; * * let x = get(list, 2).inspect((_v) => { * has_inspected = true; * }); * * assert_eq!(x, Some(3)); * assert_eq!(has_inspected, true); * ``` * * @since 0.1.0-alpha */ inspect(f: (value: T) => void): Option<T>; /** * Returns `true` if the option is a `None` value. * * ### Example * * ```ts * let x: Option<number> * * x = Some(2) * assert_eq!(x.is_none(), false) * * x = None * assert_eq!(x.is_none(), true) * ``` * * @since 0.1.0-alpha */ is_none(): boolean; /** * Returns `true` if the option is a `None` * or the value inside of it matches a predicate. * * ### Example * * ```ts * let x: Option<number> * * x = Some(2) * assert_eq!(x.is_none_or((v) => v > 1), true) * * x = Some(0) * assert_eq!(x.is_none_or((v) => v > 1), false) * * x = None * assert_eq!(x.is_none_or((v) => v > 1), true) * ``` * * @since 0.1.0-alpha */ is_none_or(f: (value: T) => boolean): boolean; /** * Returns `true` if the option is a `Some` value. * * ### Example * * ```ts * let x: Option<number> * * x = Some(2) * assert_eq!(x.is_some(), true) * * x = None * assert_eq!(x.is_some(), false) * ``` * * @since 0.1.0-alpha */ is_some(): boolean; /** * Checks if the `Option` is `Some` and the value satisfies a predicate. * * ### Example * * ```ts * let x: Option<number> * * x = Some(2) * assert_eq!(x.is_some_and((v) => v > 1), true) * * x = Some(0) * assert_eq!(x.is_some_and((v) => v > 1), false) * * x = None * assert_eq!(x.is_some_and((v) => v > 1), false) * ``` * * @since 0.1.0-alpha */ is_some_and(f: (value: T) => boolean): boolean; /** * Maps an `Option<T>` to `Option<U>` by applying a function `f` * to a contained value (if `Some`) or returns `None` (if `None`). * * ### Example * * ```ts * let x: Option<string> * * x = Some("Hello, World!") * assert_eq!(x.map((s) => s.length), Some(13)) * * x = None * assert_eq!(x.map((s) => s.length), None) * ``` * * @since 0.1.0-alpha */ map<U>(f: (value: T) => U): Option<T | U>; /** * Returns the provided default result (if none), * or applies a function `f` to the contained value (if any). * * If you are passing the result of a function call, * it is recommended to use `map_or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Option<string> * * x = Some("foo") * assert_eq!(x.map_or(42, (v) => v.length), 3) * * x = None * assert_eq!(x.map_or(42, (v) => v.length), 42) * ``` * * @since 0.1.0-alpha */ map_or<U>(default_value: U, f: (value: T) => U): U; /** * Computes a default function result (if none), * or applies a different function to the contained value (if any). * * ### Example * * ```ts * const k = 21 * let x: Option<string> * * x = Some("foo") * assert_eq!(x.map_or_else(() => 2 * k, (v) => v.length), 3) * * x = None * assert_eq!(x.map_or_else(() => 2 * k, (v) => v.length), 42) * ``` * * @since 0.1.0-alpha */ map_or_else<U>(default_f: () => U, f: (value: T) => U): U; /** * Transforms the `Option<T>` into a `Result<T, E>`, * mapping `Some(value)` to `Ok(value)` and `None` to `Err(err)`. * * Arguments passed to `ok_or` are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use` ok_or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Option<number> * let y: Result<number, string> * * x = Some(42) * y = x.ok_or("Not found") * assert_eq!(y, Ok(42)) * * x = None * y = x.ok_or("Not found") * assert_eq!(y, Err("Not found")) * ``` * * @since 0.2.0-beta */ ok_or<E>(err: E): Result<T, E>; // TODO: ok_or_else<E, F>(err: F): Result<T, E> /** * Returns the option if it contains a value, otherwise returns `optb`. * * Arguments passed to or are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Option<number> * let y: Option<number> * * x = Some(2) * y = None * assert_eq!(x.or(y), Some(2)) * * x = None * y = Some(100) * assert_eq!(x.or(y), Some(100)) * * x = Some(2) * y = Some(100) * assert_eq!(x.or(y), Some(2)) * * x = None * y = None * assert_eq!(x.or(y), None) * ``` * * @since 0.1.0-alpha */ or(optb: Option<T>): Option<T>; /** * Returns the option if it contains a value, * otherwise calls `f` and returns the result. * * ### Example * * ```ts * let x: Option<string> * let y: Option<string> * * x = Some("barbarians") * y = Some("vikings") * assert_eq!( * x.or_else(() => y), * Some("barbarians"), * ) * * x = None * y = Some("vikings") * assert_eq!( * x.or_else(() => y), * Some("vikings"), * ) * * x = None * y = None * assert_eq!( * x.or_else(() => y), * None, * ) * ``` * * @since 0.1.0-alpha */ or_else(f: () => Option<T>): Option<T>; /** * @ignore * * Returns a string representing this object. * This method is meant to be overridden by derived JS objects * for custom type coercion logic. * * ### Example * * ```ts * let x: Option<unknown> * * x = Some(true) * assert_eq!(x.toString(), "Some(true)") * * x = Some(42) * assert_eq!(x.toString(), "Some(42)") * * x = Some("hello") * assert_eq!(x.toString(), "Some(hello)") * * x = Some([1, 2]) * assert_eq!(x.toString(), "Some(1,2)") * * x = Some({}) * assert_eq!(x.toString(), "Some([object Object])") * * x = Some(() => 2 * 4) * assert_eq!(x.toString(), "Some(() => 2 * 4)") * * x = None * assert_eq!(x.toString(), "None") * ``` * * @since 0.1.0-alpha */ toString(): string; // TODO: transpose(): Result<Option<T>, E> /** * Returns the contained `Some` value. Panics if it is `None`. * * Because this function may throw a TypeError, its use is generally discouraged. * Errors are meant for unrecoverable errors, and do abort the entire program. * * Instead, prefer to use try/catch, promise or pattern matching * and handle the `None` case explicitly, or call `unwrap_or` or `unwrap_or_else`. * * ### Example * * ```ts * let x: Option<string>; * * x = Some("air"); * assert_eq!(x.unwrap(), "air"); * * x = None; * assert_err!( * () => x.unwrap(), * TypeError, * "Called Option.unwrap() on a None value", * ); * ``` * * @since 0.1.0-alpha */ unwrap(): T; /** * Returns the contained `Some` value or a provided default value. * * Arguments passed to `unwrap_or` are eagerly evaluated; * if you are passing the result of a function call, * it is recommended to use `unwrap_or_else`, which is lazily evaluated. * * ### Example * * ```ts * let x: Option<number> * * x = Some(42) * assert_eq!(x.unwrap_or(1), 42) * * x = None * assert_eq!(x.unwrap_or(1), 1) * ``` * * @since 0.1.0-alpha */ unwrap_or(default_value: T): T; /** * Returns the contained `Some` value or computes it from a closure. * * Useful for expensive default computations. * * ### Example * * ```ts * const k = 10 * let x: Option<number> * * x = Some(4) * assert_eq!(x.unwrap_or_else(() => 2 * k), 4) * * x = None * assert_eq!(x.unwrap_or_else(() => 2 * k), 20) * ``` * * @since 0.1.0-alpha */ unwrap_or_else(f: () => T): T; /** * Returns `Some` if exactly one of itself, `optb` is `Some`, * otherwise returns `None`. * * ### Example * * ```ts * let x: Option<number> * let y: Option<number> * * x = Some(2) * y = None * assert_eq!(x.xor(y), Some(2)) * * x = None * y = Some(100) * assert_eq!(x.xor(y), Some(100)) * * x = Some(2) * y = Some(100) * assert_eq!(x.xor(y), None) * * x = None * y = None * assert_eq!(x.xor(y), None) * ``` * * @since 0.1.0-alpha */ xor(optb: Option<T>): Option<T>; } /** * @internal * * Unique id for Some */ const sid = Symbol.for("@@option/some"); /** * @internal * * Unique id for Some */ const nid = Symbol.for("@@option/none"); /** * @internal * @inheritdoc * * Option constructor */ class OptionConstructor<T> implements Option<T> { /** * @internal * @private */ private _extract(): T { if (this.is_none()) { throw new TypeError("Prevent taking value from `None`."); } return (this as any)[sid] as T; } public constructor(_id: typeof nid); public constructor(_id: typeof sid, value: T); public constructor(_id: typeof sid | typeof nid, value?: T) { if (_id === sid) { (this as any)[_id] = value; } else if (_id === nid) { (this as any)[_id] = undefined; } else { throw new TypeError("Unknown constructor id"); } } /** @inheritdoc */ public and<U>(optb: Option<U>): Option<T | U> { if (this.is_some()) { return optb; } return new OptionConstructor<T>(nid); } /** @inheritdoc */ public and_then<U>(f: (value: T) => Option<U>): Option<T | U> { if (this.is_some()) { return f(this._extract()); } return new OptionConstructor<T>(nid); } /** @inheritdoc */ public expect(msg: string): T { if (this.is_some()) { return this._extract(); } throw new Error(msg); } /** @inheritdoc */ public filter(predicate: (value: T) => boolean): Option<T> { if (this.is_some() && predicate(this._extract())) { return new OptionConstructor<T>(sid, this._extract()); } return new OptionConstructor<T>(nid); } /** @inheritdoc */ public flatten<U>(this: Option<Option<U>>): Option<U> { if (this.is_some()) { return (this as any)._extract(); } return new OptionConstructor<U>(nid); } /** @inheritdoc */ public inspect(f: (value: T) => void): Option<T> { if (this.is_some()) { f(this._extract()); } return this; } /** @inheritdoc */ public is_none(): boolean { return nid in this; } /** @inheritdoc */ public is_none_or(f: (value: T) => boolean): boolean { if (this.is_some()) { return f(this._extract()); } return true; } /** @inheritdoc */ public is_some(): boolean { return sid in this; } /** @inheritdoc */ public is_some_and(f: (value: T) => boolean): boolean { if (this.is_some()) { return f(this._extract()); } return false; } /** @inheritdoc */ public map<U>(f: (value: T) => U): Option<T | U> { if (this.is_some()) { return new OptionConstructor<U>(sid, f(this._extract())); } return new OptionConstructor<T>(nid); } /** @inheritdoc */ public map_or<U>(default_value: U, f: (value: T) => U): U { if (this.is_some()) { return f(this._extract()); } return default_value; } /** @inheritdoc */ public map_or_else<U>(default_f: () => U, f: (value: T) => U): U { if (this.is_some()) { return f(this._extract()); } return default_f(); } /** @inheritdoc */ public ok_or<E>(err: E): Result<T, E> { let result: Result<T, E>; if (this.is_some()) { result = Ok(this._extract()); } else { result = Err(err); } return result; } // TODO: ok_or_else<E, F>(err: F): Result<T, E> /** @inheritdoc */ public or(optb: Option<T>): Option<T> { if (this.is_some()) { return new OptionConstructor<T>(sid, this._extract()); } return optb; } /** @inheritdoc */ public or_else(f: () => Option<T>): Option<T> { if (this.is_some()) { return new OptionConstructor<T>(sid, this._extract()); } return f(); } /** @inheritdoc */ public toString(): string { return this.is_some() ? `Some(${this._extract()})` : "None"; } /** * Overrides Node.js object inspection. * * @see toString * * @ignore */ public [Symbol.for("nodejs.util.inspect.custom")](): string { return this.toString(); } // TODO: transpose(): Result<Option<T>, E> /** @inheritdoc */ public unwrap(): T { if (this.is_some()) { return this._extract(); } throw new TypeError("Called Option.unwrap() on a None value"); } /** @inheritdoc */ public unwrap_or(default_value: T): T { if (this.is_some()) { return this._extract(); } return default_value; } /** @inheritdoc */ public unwrap_or_else(f: () => T): T { if (this.is_some()) { return this._extract(); } return f(); } /** @inheritdoc */ public xor(optb: Option<T>): Option<T> { if (this.is_some()) { return optb.is_none() ? new OptionConstructor<T>(sid, this._extract()) : new OptionConstructor<T>(nid); } return optb.is_some() ? optb : new OptionConstructor<T>(nid); } } /** * Some value of type T. * * ### Example * * ```ts * let x: Option<number> = Some(42) * ``` * * @since 0.1.0-alpha */ export function Some<T>(value: T): Option<T> { return new OptionConstructor<T>(sid, value); } /** * No value. * * ### Example * * ```ts * let x: Option<number> = None * ``` * * @since 0.1.0-alpha */ export const None: Option<any> = new OptionConstructor<any>(nid);