/** [[include:doc/maybe.md]] */ /** (keep typedoc from getting confused by the imports) */ import * as Result from './result'; import { curry1, isVoid } from './utils'; /** Discriminant for the `Just` and `Nothing` variants. You can use the discriminant via the `variant` property of `Maybe` instances if you need to match explicitly on it. */ export var Variant; (function (Variant) { Variant["Just"] = "Just"; Variant["Nothing"] = "Nothing"; })(Variant || (Variant = {})); /** A `Just` instance is the *present* variant instance of the [`Maybe`](../modules/_maybe_.html#maybe) type, representing the presence of a value which may be absent. For a full discussion, see [the module docs](../modules/_maybe_.html). @typeparam T The type wrapped in this `Just` variant of `Maybe`. */ export class Just { /** Create an instance of `Maybe.Just` with `new`. @note While you *may* create the `Just` type via normal JavaScript class construction, it is not recommended for the functional style for which the library is intended. Instead, use [`Maybe.of`] (for the general case) or [`Maybe.just`] for this specific case. [`Maybe.of`]: ../modules/_maybe_.html#of [`Maybe.just`]: ../modules/_maybe_.html#just ```ts // Avoid: const aString = new Maybe.Just('characters'); // Prefer: const aString = Maybe.just('characters); ``` @param value The value to wrap in a `Maybe.Just`. `null` and `undefined` are allowed by the type signature so that the constructor may `throw` on those rather than constructing a type like `Maybe`. @throws If you pass `null` or `undefined`. */ constructor(value) { /** `Just` is always [`Variant.Just`](../enums/_maybe_.variant#just). */ this.variant = Variant.Just; if (isVoid(value)) { throw new Error('Tried to construct `Just` with `null` or `undefined`'); } this.value = value; } /** Unwrap the contained value. A convenience method for functional idioms. A common scenario where you might want to use this is in a pipeline of functions: ```ts import Maybe, { Just } from 'true-myth/maybe'; function getLengths(maybeStrings: Array>): Array { return maybeStrings .filter(Maybe.isJust) .map(Just.unwrap) .map(s => s.length); } ``` */ static unwrap(theJust) { return theJust.value; } /** Method variant for [`Maybe.isJust`](../modules/_maybe_.html#isjust) */ isJust() { return true; } /** Method variant for [`Maybe.isNothing`](../modules/_maybe_.html#isnothing) */ isNothing() { return false; } /** Method variant for [`Maybe.map`](../modules/_maybe_.html#map) */ map(mapFn) { return map(mapFn, this); } /** Method variant for [`Maybe.mapOr`](../modules/_maybe_.html#mapor) */ mapOr(orU, mapFn) { return mapOr(orU, mapFn, this); } /** Method variant for [`Maybe.mapOrElse`](../modules/_maybe_.html#maporelse) */ mapOrElse(orElseFn, mapFn) { return mapOrElse(orElseFn, mapFn, this); } /** Method variant for [`Maybe.match`](../modules/_maybe_.html#match) */ match(matcher) { return match(matcher, this); } /** Method variant for [`Maybe.or`](../modules/_maybe_.html#or) */ or(mOr) { return or(mOr, this); } /** Method variant for [`Maybe.orElse`](../modules/_maybe_.html#orelse) */ orElse(orElseFn) { return orElse(orElseFn, this); } /** Method variant for [`Maybe.and`](../modules/_maybe_.html#and) */ and(mAnd) { return and(mAnd, this); } /** Method variant for [`Maybe.andThen`](../modules/_maybe_.html#andthen) */ andThen(andThenFn) { return andThen(andThenFn, this); } /** Method variant for [`Maybe.chain`](../modules/_maybe_.html#chain) */ chain(chainFn) { return this.andThen(chainFn); } /** Method variant for [`Maybe.flatMap`](../modules/_maybe_.html#flatmap) */ flatMap(flatMapFn) { return this.andThen(flatMapFn); } /** Method variant for [`Maybe.unsafelyUnwrap`](../modules/_maybe_.html#unsafelyunwrap) */ unsafelyUnwrap() { return this.value; } /** Method variant for [`Maybe.unwrapOr`](../modules/_maybe_.html#unwrapor) */ unwrapOr(defaultValue) { return unwrapOr(defaultValue, this); } /** Method variant for [`Maybe.unwrapOrElse`](../modules/_maybe_.html#unwraporelse) */ unwrapOrElse(elseFn) { return unwrapOrElse(elseFn, this); } /** Method variant for [`Maybe.toOkOrErr`](../modules/_maybe_.html#tookorerr) */ toOkOrErr(error) { return toOkOrErr(error, this); } /** Method variant for [`Maybe.toOkOrElseErr`](../modules/_maybe_.html#tookorelseerr) */ toOkOrElseErr(elseFn) { return toOkOrElseErr(elseFn, this); } /** Method variant for [`Maybe.toString`](../modules/_maybe_.html#tostring) */ toString() { return toString(this); } /** Method variant for [`Maybe.toJSON`](../modules/_maybe_.html#toJSON) */ toJSON() { return toJSON(this); } /** Method variant for [`Maybe.equals`](../modules/_maybe_.html#equals) */ equals(comparison) { return equals(comparison, this); } /** Method variant for [`Maybe.ap`](../modules/_maybe_.html#ap) */ ap(val) { return ap(this, val); } /** Method variant for [`Maybe.get`](../modules/_maybe_.html#prop) If you have a `Maybe` of an object type, you can do `thatMaybe.get('a key')` to look up the next layer down in the object. ```ts type DeepOptionalType = { something?: { with?: { deeperKeys?: string; } } }; const fullySet: DeepType = { something: { with: { deeperKeys: 'like this' } } }; const deepJust = Maybe.of(fullySet) .get('something') .get('with') .get('deeperKeys'); console.log(deepJust); // Just('like this'); const partiallyUnset: DeepType = { something: { } }; const deepEmpty = Maybe.of(partiallyUnset) .get('something') .get('with') .get('deeperKeys'); console.log(deepEmpty); // Nothing ``` */ get(key) { return this.andThen(property(key)); } } /** A single instance of the `Nothing` object, to minimize memory usage. No matter how many `Maybe`s are floating around, there will always be exactly and only one `Nothing`. @private */ let NOTHING; /** A `Nothing` instance is the *absent* variant instance of the [`Maybe`](../modules/_maybe_.html#maybe) type, representing the presence of a value which may be absent. For a full discussion, see [the module docs](../modules/_maybe_.html). @typeparam T The type which would be wrapped in a `Just` variant of `Maybe`. */ export class Nothing { /** Create an instance of `Maybe.Nothing` with `new`. @note While you *may* create the `Nothing` type via normal JavaScript class construction, it is not recommended for the functional style for which the library is intended. Instead, use [`Maybe.of`] (for the general case) or [`Maybe.nothing`] for this specific case. [`Maybe.of`]: ../modules/_maybe_.html#of [`Maybe.nothing`]: ../modules/_maybe_.html#nothing ```ts // Avoid: const aNothing = new Maybe.Err(); // Prefer: const aNothing = Maybe.nothing(); ``` `null` and `undefined` are allowed so that you may explicitly construct the `Err` type with a known `null` or `undefined` value. (This maybe helpful primarily when transitioning a codebase to the use of `Maybe`.) @throws If you pass `null` or `undefined`. */ constructor(_) { /** `Nothing` is always [`Variant.Nothing`](../enums/_maybe_.variant#nothing). */ this.variant = Variant.Nothing; if (!NOTHING) { NOTHING = this; } return NOTHING; } /** Method variant for [`Maybe.isJust`](../modules/_maybe_.html#isjust) */ isJust() { return false; } /** Method variant for [`Maybe.isNothing`](../modules/_maybe_.html#isnothing) */ isNothing() { return true; } /** Method variant for [`Maybe.map`](../modules/_maybe_.html#map) */ map(mapFn) { return map(mapFn, this); } /** Method variant for [`Maybe.mapOr`](../modules/_maybe_.html#mapor) */ mapOr(orU, mapFn) { return mapOr(orU, mapFn, this); } /** Method variant for [`Maybe.mapOrElse`](../modules/_maybe_.html#maporelse) */ mapOrElse(orElseFn, mapFn) { return mapOrElse(orElseFn, mapFn, this); } /** Method variant for [`Maybe.match`](../modules/_maybe_.html#match) */ match(matcher) { return match(matcher, this); } /** Method variant for [`Maybe.or`](../modules/_maybe_.html#or) */ or(mOr) { return or(mOr, this); } /** Method variant for [`Maybe.orElse`](../modules/_maybe_.html#orelse) */ orElse(orElseFn) { return orElse(orElseFn, this); } /** Method variant for [`Maybe.and`](../modules/_maybe_.html#and) */ and(mAnd) { return and(mAnd, this); } /** Method variant for [`Maybe.andThen`](../modules/_maybe_.html#andthen) */ andThen(andThenFn) { return andThen(andThenFn, this); } /** Method variant for [`Maybe.chain`](../modules/_maybe_.html#chain) */ chain(chainFn) { return this.andThen(chainFn); } /** Method variant for [`Maybe.flatMap`](../modules/_maybe_.html#flatmap) */ flatMap(flatMapFn) { return this.andThen(flatMapFn); } /** Method variant for [`Maybe.unsafelyUnwrap`](../modules/_maybe_.html#unsafelyunwrap) */ unsafelyUnwrap() { throw new Error('Tried to `unsafelyUnwrap(Nothing)`'); } /** Method variant for [`Maybe.unwrapOr`](../modules/_maybe_.html#unwrapor) */ unwrapOr(defaultValue) { return unwrapOr(defaultValue, this); } /** Method variant for [`Maybe.unwrapOrElse`](../modules/_maybe_.html#unwraporelse) */ unwrapOrElse(elseFn) { return unwrapOrElse(elseFn, this); } /** Method variant for [`Maybe.toOkOrErr`](../modules/_maybe_.html#tookorerr) */ toOkOrErr(error) { return toOkOrErr(error, this); } /** Method variant for [`Maybe.toOkOrElseErr`](../modules/_maybe_.html#tookorelseerr) */ toOkOrElseErr(elseFn) { return toOkOrElseErr(elseFn, this); } /** Method variant for [`Maybe.toString`](../modules/_maybe_.html#tostring) */ toString() { return toString(this); } /** Method variant for [`Maybe.toJSON`](../modules/_maybe_.html#toJSON) */ toJSON() { return toJSON(this); } /** Method variant for [`Maybe.equals`](../modules/_maybe_.html#equals) */ equals(comparison) { return equals(comparison, this); } /** Method variant for [`Maybe.ap`](../modules/_maybe_.html#ap) */ ap(val) { return ap(this, val); } /** Method variant for [`Maybe.get`](../modules/_maybe_.html#prop) If you have a `Maybe` of an object type, you can do `thatMaybe.get('a key')` to look up the next layer down in the object. ```ts type DeepOptionalType = { something?: { with?: { deeperKeys?: string; } } }; const fullySet: DeepType = { something: { with: { deeperKeys: 'like this' } } }; const deepJust = Maybe.of(fullySet) .get('something') .get('with') .get('deeperKeys'); console.log(deepJust); // Just('like this'); const partiallyUnset: DeepType = { something: { } }; const deepEmpty = Maybe.of(partiallyUnset) .get('something') .get('with') .get('deeperKeys'); console.log(deepEmpty); // Nothing ``` */ get(key) { return this.andThen(property(key)); } } /** Is this result a `Just` instance? @typeparam T The type of the wrapped value. @param maybe The `Maybe` instance to check. @returns `true` if `maybe` is `Just`, `false` otherwise. In TypeScript, also narrows the type from `Maybe` to `Just`. */ export function isJust(maybe) { return maybe.variant === Variant.Just; } /** Is this result a `Nothing` instance? @typeparam T The type of the wrapped value. @param maybe The `Maybe` instance to check. @returns `true` if `maybe` is `nothing`, `false` otherwise. In TypeScript, also narrows the type from `Maybe` to `Nothing`. */ export function isNothing(maybe) { return maybe.variant === Variant.Nothing; } /** Create an instance of `Maybe.Just`. `null` and `undefined` are allowed by the type signature so that the function may `throw` on those rather than constructing a type like `Maybe`. @typeparam T The type of the item contained in the `Maybe`. @param value The value to wrap in a `Maybe.Just`. @returns An instance of `Maybe.Just`. @throws If you pass `null` or `undefined`. */ export function just(value) { return new Just(value); } /** Create an instance of `Maybe.Nothing`. If you want to create an instance with a specific type, e.g. for use in a function which expects a `Maybe` where the `` is known but you have no value to give it, you can use a type parameter: ```ts const notString = Maybe.nothing(); ``` @typeparam T The type of the item contained in the `Maybe`. @returns An instance of `Maybe.Nothing`. */ export function nothing(_) { if (!NOTHING) NOTHING = new Nothing(); return NOTHING; } /** Create a `Maybe` from any value. To specify that the result should be interpreted as a specific type, you may invoke `Maybe.of` with an explicit type parameter: ```ts const foo = Maybe.of(null); ``` This is usually only important in two cases: 1. If you are intentionally constructing a `Nothing` from a known `null` or undefined value *which is untyped*. 2. If you are specifying that the type is more general than the value passed (since TypeScript can define types as literals). @typeparam T The type of the item contained in the `Maybe`. @param value The value to wrap in a `Maybe`. If it is `undefined` or `null`, the result will be `Nothing`; otherwise it will be the type of the value passed. */ export function of(value) { return isVoid(value) ? nothing() : just(value); } /** Alias for [`of`](#of), convenient for a standalone import: ```ts import { maybe } from 'true-myth/maybe'; interface Dict { [key: string]: T | null | undefined; } interface StrictDict { [key: string]: Maybe; } function wrapNullables(dict: Dict): StrictDict { return Object.keys(dict).reduce((strictDict, key) => { strictDict[key] = maybe(dict[key]); return strictDict; }, {} as StrictDict); } ``` */ export const maybe = of; /** Alias for [`of`](#of), primarily for compatibility with Folktale. */ export const fromNullable = of; export function map(mapFn, maybe) { const op = (m) => (m.isJust() ? just(mapFn(m.value)) : nothing()); return curry1(op, maybe); } export function mapOr(orU, mapFn, maybe) { function fullOp(fn, m) { return m.isJust() ? fn(m.value) : orU; } function partialOp(fn, curriedMaybe) { return curriedMaybe !== undefined ? fullOp(fn, curriedMaybe) : (extraCurriedMaybe) => fullOp(fn, extraCurriedMaybe); } return mapFn === undefined ? partialOp : maybe === undefined ? partialOp(mapFn) : partialOp(mapFn, maybe); } export function mapOrElse(orElseFn, mapFn, maybe) { function fullOp(fn, m) { return m.isJust() ? fn(m.value) : orElseFn(); } function partialOp(fn, curriedMaybe) { return curriedMaybe !== undefined ? fullOp(fn, curriedMaybe) : (extraCurriedMaybe) => fullOp(fn, extraCurriedMaybe); } if (mapFn === undefined) { return partialOp; } else if (maybe === undefined) { return partialOp(mapFn); } else { return partialOp(mapFn, maybe); } } export function and(andMaybe, maybe) { const op = (m) => (m.isJust() ? andMaybe : nothing()); return curry1(op, maybe); } export function andThen(thenFn, maybe) { const op = (m) => (m.isJust() ? thenFn(m.value) : nothing()); return maybe !== undefined ? op(maybe) : op; } /** Alias for [`andThen`](#andthen). */ export const chain = andThen; /** Alias for [`andThen`](#andthen). */ export const flatMap = andThen; /** Alias for [`andThen`](#andthen). */ export const bind = andThen; export function or(defaultMaybe, maybe) { const op = (m) => (m.isJust() ? m : defaultMaybe); return maybe !== undefined ? op(maybe) : op; } export function orElse(elseFn, maybe) { const op = (m) => (m.isJust() ? m : elseFn()); return curry1(op, maybe); } /** Get the value out of the `Maybe`. Returns the content of a `Just`, but **throws if the `Maybe` is `Nothing`**. Prefer to use [`unwrapOr`](#unwrapor) or [`unwrapOrElse`](#unwraporelse). @typeparam T The type of the wrapped value. @param maybe The value to unwrap @returns The unwrapped value if the `Maybe` instance is `Just`. @throws If the `maybe` is `Nothing`. */ export function unsafelyUnwrap(maybe) { return maybe.unsafelyUnwrap(); } /** Alias for [`unsafelyUnwrap`](#unsafelyunwrap) */ export const unsafelyGet = unsafelyUnwrap; /** Alias for [`unsafelyUnwrap`](#unsafelyunwrap) */ export const unsafeGet = unsafelyUnwrap; export function unwrapOr(defaultValue, maybe) { const op = (m) => (m.isJust() ? m.value : defaultValue); return curry1(op, maybe); } /** Alias for [`unwrapOr`](#unwrapor) */ export const getOr = unwrapOr; export function unwrapOrElse(orElseFn, maybe) { const op = (m) => (m.isJust() ? m.value : orElseFn()); return curry1(op, maybe); } /** Alias for [`unwrapOrElse`](#unwraporelse) */ export const getOrElse = unwrapOrElse; export function toOkOrErr(error, maybe) { const op = (m) => (m.isJust() ? Result.ok(m.value) : Result.err(error)); return maybe !== undefined ? op(maybe) : op; } export function toOkOrElseErr(elseFn, maybe) { const op = (m) => (m.isJust() ? Result.ok(m.value) : Result.err(elseFn())); return curry1(op, maybe); } /** Construct a `Maybe` from a `Result`. If the `Result` is an `Ok`, wrap its value in `Just`. If the `Result` is an `Err`, throw away the wrapped `E` and transform to a `Nothing`. @typeparam T The type of the value wrapped in a `Result.Ok` and in the `Just` of the resulting `Maybe`. @param result The `Result` to construct a `Maybe` from. @returns `Just` if `result` was `Ok` or `Nothing` if it was `Err`. */ export function fromResult(result) { return result.isOk() ? just(result.value) : nothing(); } /** Create a `String` representation of a `Maybe` instance. A `Just` instance will be printed as `Just()`, where the representation of the value is simply the value's own `toString` representation. For example: | call | output | |----------------------------------------|-------------------------| | `toString(Maybe.of(42))` | `Just(42)` | | `toString(Maybe.of([1, 2, 3]))` | `Just(1,2,3)` | | `toString(Maybe.of({ an: 'object' }))` | `Just([object Object])` | | `toString(Maybe.nothing())` | `Nothing` | @typeparam T The type of the wrapped value; its own `.toString` will be used to print the interior contents of the `Just` variant. @param maybe The value to convert to a string. @returns The string representation of the `Maybe`. */ export function toString(maybe) { const body = maybe.isJust() ? `(${maybe.value.toString()})` : ''; return `${maybe.variant}${body}`; } /** * Create an `Object` representation of a `Maybe` instance. * * Useful for serialization. `JSON.stringify()` uses it. * * @param maybe The value to convert to JSON * @returns The JSON representation of the `Maybe` */ export function toJSON(maybe) { return maybe.isJust() ? { variant: maybe.variant, value: maybe.value } : { variant: maybe.variant }; } export function match(matcher, maybe) { return maybe !== undefined ? mapOrElse(matcher.Nothing, matcher.Just, maybe) : (curriedMaybe) => mapOrElse(matcher.Nothing, matcher.Just, curriedMaybe); } /** Alias for [`match`](#match) */ export const cata = match; export function equals(mb, ma) { return ma !== undefined ? ma.match({ Just: aVal => mb.isJust() && mb.unsafelyUnwrap() === aVal, Nothing: () => isNothing(mb), }) : (maybeA) => maybeA.match({ Nothing: () => isNothing(mb), Just: aVal => mb.isJust() && mb.unsafelyUnwrap() === aVal, }); } export function ap(maybeFn, maybe) { const op = (m) => m.match({ Just: val => maybeFn.map(fn => fn(val)), Nothing: () => nothing(), }); return curry1(op, maybe); } /** Determine whether an item is an instance of `Just` or `Nothing`. @param item The item to check. */ export function isInstance(item) { return item instanceof Just || item instanceof Nothing; } export function find(predicate, array) { const op = (a) => maybe(a.find(predicate)); return curry1(op, array); } /** Safely get the first item from a list, returning `Just` the first item if the array has at least one item in it, or `Nothing` if it is empty. ## Examples ```ts let empty = []; Maybe.head(empty); // => Nothing let full = [1, 2, 3]; Maybe.head(full); // => Just(1) ``` @param array The array to get the first item from. */ export function head(array) { return maybe(array[0]); } /** A convenience alias for `Maybe.head`. */ export const first = head; /** Safely get the last item from a list, returning `Just` the last item if the array has at least one item in it, or `Nothing` if it is empty. ## Examples ```ts let empty = []; Maybe.last(empty); // => Nothing let full = [1, 2, 3]; Maybe.last(full); // => Just(3) ``` @param array The array to get the first item from. */ export function last(array) { return maybe(array[array.length - 1]); } /** Convert the arguments to a single `Maybe`. Useful for dealing with arrays of `Maybe`s, via the spread operator. ## Examples ```ts import Maybe from 'true-myth/maybe'; let valid = [Maybe.just(2), Maybe.just('three')]; Maybe.all(...valid); // => Just([2, 'three']); let invalid = [Maybe.just(2), Maybe.nothing()]; Maybe.all(...invalid); // => Nothing ``` ## Note on Spread This requires the use of the spread operator because (at least as of TypeScript 3.0), the type inference falls down when attempting to build this same type with an array directly. Moreover, this spread-based approach handles heteregenous arrays; TS *also* fails to infer correctly for anything but homogeneous arrays when using that approach. @param maybes The `Maybe`s to resolve to a single `Maybe`. */ export function all(...maybes) { let result = just([]); maybes.forEach(maybe => { result = result.andThen(accumulatedMaybes => maybe.map(m => { accumulatedMaybes.push(m); return accumulatedMaybes; })); }); return result; } export function tuple(maybes) { // @ts-ignore -- this doesn't type-check, but it works correctly. return all(...maybes); } export function property(key, obj) { const op = (a) => maybe(a[key]); return curry1(op, obj); } export function get(key, maybeObj) { return curry1(bind(property(key)), maybeObj); } /** Transform a function from a normal JS function which may return `null` or `undefined` to a function which returns a `Maybe` instead. For example, dealing with the `Document#querySelector` DOM API involves a *lot* of things which can be `null`: ```ts const foo = document.querySelector('#foo'); let width: number; if (foo !== null) { width = foo.getBoundingClientRect().width; } else { width = 0; } const getStyle = (el: HTMLElement, rule: string) => el.style[rule]; const bar = document.querySelector('.bar'); let color: string; if (bar != null) { let possibleColor = getStyle(bar, 'color'); if (possibleColor !== null) { color = possibleColor; } else { color = 'black'; } } ``` (Imagine in this example that there were more than two options: the simplifying workarounds you commonly use to make this terser in JS, like the ternary operator or the short-circuiting `||` operator, eventually become very confusing with more complicated flows.) We can work around this with `Maybe`, always wrapping each layer in `Maybe.of` invocations, and this is *somewhat* better: ```ts const aWidth = Maybe.of(document.querySelector('#foo')) .map(el => el.getBoundingClientRect().width) .unwrapOr(0); const aColor = Maybe.of(document.querySelector('.bar')) .andThen(el => Maybe.of(getStyle(el, 'color')) .unwrapOr('black'); ``` With `wrapReturn`, though, you can create a transformed version of a function *once* and then be able to use it freely throughout your codebase, *always* getting back a `Maybe`: ```ts const querySelector = Maybe.wrapReturn(document.querySelector.bind(document)); const safelyGetStyle = Maybe.wrapReturn(getStyle); const aWidth = querySelector('#foo') .map(el => el.getBoundingClientRect().width) .unwrapOr(0); const aColor = querySelector('.bar') .andThen(el => safelyGetStyle(el, 'color')) .unwrapOr('black'); ``` @param fn The function to transform; the resulting function will have the exact same signature except for its return type. */ export function wrapReturn(fn) { return (...args) => of(fn(...args)); } /** Alias for [`wrapReturn`](#wrapReturn). */ export const maybeify = wrapReturn; export const transmogrify = wrapReturn; export const Maybe = { Variant, Just, Nothing, all, isJust, isNothing, just, nothing, of, find, first, fromNullable, head, last, map, mapOr, mapOrElse, and, andThen, chain, flatMap, or, orElse, unsafelyUnwrap, unsafelyGet, unsafeGet, unwrapOr, getOr, unwrapOrElse, getOrElse, toOkOrErr, toOkOrElseErr, fromResult, toString, toJSON, tuple, match, cata, equals, ap, isInstance, property, get, wrapReturn, ify: wrapReturn, }; export default Maybe; //# sourceMappingURL=maybe.js.map