886 lines
27 KiB
JavaScript
886 lines
27 KiB
JavaScript
/** [[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<undefined>`.
|
|
|
|
@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<Maybe<string>>): Array<number> {
|
|
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<T>` to `Just<T>`.
|
|
*/
|
|
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<T>` to `Nothing<T>`.
|
|
*/
|
|
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<undefined>`.
|
|
|
|
@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<T>`.
|
|
@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<T>` where the `<T>` is known but you have no
|
|
value to give it, you can use a type parameter:
|
|
|
|
```ts
|
|
const notString = Maybe.nothing<string>();
|
|
```
|
|
|
|
@typeparam T The type of the item contained in the `Maybe`.
|
|
@returns An instance of `Maybe.Nothing<T>`.
|
|
*/
|
|
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<string>(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<T> {
|
|
[key: string]: T | null | undefined;
|
|
}
|
|
|
|
interface StrictDict<T> {
|
|
[key: string]: Maybe<T>;
|
|
}
|
|
|
|
function wrapNullables<T>(dict: Dict<T>): StrictDict<T> {
|
|
return Object.keys(dict).reduce((strictDict, key) => {
|
|
strictDict[key] = maybe(dict[key]);
|
|
return strictDict;
|
|
}, {} as StrictDict<T>);
|
|
}
|
|
```
|
|
*/
|
|
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<T>` from a `Result<T, E>`.
|
|
|
|
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(<representation of the value>)`,
|
|
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<string>()];
|
|
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
|