/** * https://github.com/gigobyte/purify * Copyright (c) 2018, Stanislav Iliev. Licensed under MIT. */ import { Either, Left, Right } from './Either.js' export type MaybePatterns = | { Just: (value: T) => U; Nothing: () => U } | { _: () => U } interface AlwaysJust { kind: '$$MaybeAlwaysJust' } type ExtractMaybe = T extends never ? TDefault : T | TDefault export interface Maybe { /** Returns true if `this` is `Just`, otherwise it returns false */ isJust(): this is AlwaysJust /** Returns true if `this` is `Nothing`, otherwise it returns false */ isNothing(): this is Nothing inspect(): string toString(): string toJSON(): T /** Compares the values inside `this` and the argument, returns true if both are Nothing or if the values are equal */ equals(other: Maybe): boolean /** Transforms the value inside `this` with a given function. Returns `Nothing` if `this` is `Nothing` */ map(f: (value: T) => U): Maybe /** Maps `this` with a `Maybe` function */ ap(maybeF: Maybe<(value: T) => U>): Maybe /** Returns the first `Just` between `this` and another `Maybe` or `Nothing` if both `this` and the argument are `Nothing` */ alt(other: Maybe): Maybe /** Lazy version of `alt` */ altLazy(other: () => Maybe): Maybe /** Transforms `this` with a function that returns a `Maybe`. Useful for chaining many computations that may result in a missing value */ chain(f: (value: T) => Maybe): Maybe /** Transforms `this` with a function that returns a nullable value. Equivalent to `m.chain(x => Maybe.fromNullable(f(x)))` */ chainNullable(f: (value: T) => U | undefined | null | void): Maybe /** Flattens nested Maybes. `m.join()` is equivalent to `m.chain(x => x)` */ join(this: Maybe>): Maybe /** Takes a reducer and an initial value and returns the initial value if `this` is `Nothing` or the result of applying the function to the initial value and the value inside `this` */ reduce(reducer: (accumulator: U, value: T) => U, initialValue: U): U /** Returns `this` if it's `Nothing`, otherwise it returns the result of applying the function argument to `this` and wrapping it in a `Just` */ extend(f: (value: Maybe) => U): Maybe /** Returns the value inside `this` or throws an error if `this` is `Nothing` */ unsafeCoerce(): T /** Structural pattern matching for `Maybe` in the form of a function */ caseOf(patterns: MaybePatterns): U /** Returns the default value if `this` is `Nothing`, otherwise it return the value inside `this` */ orDefault(defaultValue: T): T /** Lazy version of `orDefault`. Takes a function that returns the default value, that function will be called only if `this` is `Nothing` */ orDefaultLazy(getDefaultValue: () => T): T /** Returns empty list if the `Maybe` is `Nothing` or a list where the only element is the value of `Just` */ toList(): T[] /** Maps over `this` and returns the resulting value or returns the default value if `this` is `Nothing` */ mapOrDefault(f: (value: T) => U, defaultValue: U): U /** Returns the value inside `this` or undefined if `this` is `Nothing`. Use `extractNullable` if you need a null returned instead */ extract(): this extends AlwaysJust ? T : ExtractMaybe /** Returns the value inside `this` or null if `this` is `Nothing`. Use `extract` if you need an undefined returned instead */ extractNullable(): this extends AlwaysJust ? T : ExtractMaybe /** Constructs a `Right` from a `Just` or a `Left` with a provided left value if `this` is `Nothing` */ toEither(left: L): Either /** Runs an effect if `this` is `Just`, returns `this` to make chaining other methods possible */ ifJust(effect: (value: T) => any): this /** Runs an effect if `this` is `Nothing`, returns `this` to make chaining other methods possible */ ifNothing(effect: () => any): this /** Takes a predicate function and returns `this` if the predicate returns true or Nothing if it returns false */ filter(pred: (value: T) => value is U): Maybe /** Takes a predicate function and returns `this` if the predicate returns true or Nothing if it returns false */ filter(pred: (value: T) => boolean): Maybe 'fantasy-land/equals'(other: Maybe): boolean 'fantasy-land/map'(f: (value: T) => U): Maybe 'fantasy-land/ap'(maybeF: Maybe<(value: T) => U>): Maybe 'fantasy-land/alt'(other: Maybe): Maybe 'fantasy-land/chain'(f: (value: T) => Maybe): Maybe 'fantasy-land/reduce'( reducer: (accumulator: U, value: T) => U, initialValue: U ): U 'fantasy-land/extend'(f: (value: Maybe) => U): Maybe 'fantasy-land/filter'(pred: (value: T) => boolean): Maybe 'fantasy-land/filter'(pred: (value: T) => boolean): Maybe } interface MaybeTypeRef { /** Takes a value and wraps it in a `Just` */ of(value: T): Maybe /** Returns `Nothing` */ empty(): Nothing /** Returns `Nothing` */ zero(): Nothing /** Takes a value and returns `Nothing` if the value is null or undefined, otherwise a `Just` is returned */ fromNullable(value: T | undefined | null | void): Maybe /** Takes a value and returns Nothing if the value is falsy, otherwise a Just is returned */ fromFalsy(value: T | undefined | null | void): Maybe /** Takes a predicate and a value, passes the value to the predicate and returns a Just if it returns true, otherwise a Nothing is returned */ fromPredicate(pred: (value: T) => boolean): (value: T) => Maybe fromPredicate(pred: (value: T) => boolean, value: T): Maybe /** Returns only the `Just` values in a list */ catMaybes(list: Maybe[]): T[] /** Maps over a list of values and returns a list of all resulting `Just` values */ mapMaybe(f: (value: T) => Maybe): (list: T[]) => U[] mapMaybe(f: (value: T) => Maybe, list: T[]): U[] /** Calls a function that may throw and wraps the result in a `Just` if successful or `Nothing` if an error is caught */ encase(thunk: () => T): Maybe isMaybe(x: unknown): x is Maybe /** Turns a list of `Maybe`s into an `Maybe` of list if all items are `Just` */ sequence(maybes: Maybe[]): Maybe 'fantasy-land/of'(value: T): Maybe 'fantasy-land/empty'(): Nothing 'fantasy-land/zero'(): Nothing } export const Maybe: MaybeTypeRef = { of(value: T): Maybe { return just(value) }, empty(): Nothing { return nothing }, zero(): Nothing { return nothing }, fromNullable(value: T | undefined | null | void): Maybe { return value == null ? nothing : just(value) }, fromFalsy(value: T | undefined | null | void): Maybe { return value ? just(value) : nothing }, fromPredicate(pred: (value: T) => boolean, value?: T): any { switch (arguments.length) { case 1: return (value: T) => Maybe.fromPredicate(pred, value) default: return pred(value!) ? just(value!) : nothing } }, mapMaybe(f: (value: T) => Maybe, list?: T[]): any { switch (arguments.length) { case 1: return (list: T[]) => Maybe.mapMaybe(f, list) default: return Maybe.catMaybes(list!.map(f)) } }, catMaybes(list: Maybe[]): T[] { let res: T[] = [] for (const e of list) { if (e.isJust()) { res.push(e.extract()) } } return res }, encase(thunk: () => T): Maybe { try { return just(thunk()) } catch { return nothing } }, isMaybe(x: unknown): x is Maybe { return x instanceof Just || x instanceof Nothing }, sequence(maybes: Maybe[]): Maybe { let res: T[] = [] for (const m of maybes) { if (m.isJust()) { res.push(m.extract()) } else { return nothing } } return just(res) }, 'fantasy-land/of'(value: T): Maybe { return this.of(value) }, 'fantasy-land/empty'(): Nothing { return this.empty() }, 'fantasy-land/zero'(): Nothing { return this.zero() } } class Just implements Maybe { constructor(private __value: T) { } isJust(): boolean { return true } isNothing(): boolean { return false } inspect(): string { return `Just(${JSON.stringify(this.__value)})` } toString(): string { return this.inspect() } toJSON(): T { return this.__value } equals(other: Maybe): boolean { return this.extract() === other.extract() } map(f: (value: T) => U): Maybe { return just(f(this.__value)) } ap(maybeF: Maybe<(value: T) => U>): Maybe { return maybeF.isJust() ? this.map(maybeF.extract()) : nothing } alt(_: Maybe): Maybe { return this } altLazy(_: () => Maybe): Maybe { return this } chain(f: (value: T) => Maybe): Maybe { return f(this.__value) } chainNullable(f: (value: T) => U | undefined | null | void): Maybe { return Maybe.fromNullable(f(this.__value)) } join(this: Just>): Maybe { return this.__value } reduce(reducer: (accumulator: U, value: T) => U, initialValue: U): U { return reducer(initialValue, this.__value) } extend(f: (value: Maybe) => U): Maybe { return just(f(this)) } unsafeCoerce(): T { return this.__value } caseOf(patterns: MaybePatterns): U { return '_' in patterns ? patterns._() : patterns.Just(this.__value) } orDefault(_: T) { return this.__value } orDefaultLazy(_: () => T): T { return this.__value } toList(): T[] { return [this.__value] } mapOrDefault(f: (value: T) => U, _: U): U { return f(this.__value) } extract(): this extends AlwaysJust ? T : ExtractMaybe { return this.__value as this extends AlwaysJust ? T : ExtractMaybe } extractNullable(): this extends AlwaysJust ? T : ExtractMaybe { return this.__value as this extends AlwaysJust ? T : ExtractMaybe } toEither(_: L): Either { return Right(this.__value) } ifJust(effect: (value: T) => any): this { return effect(this.__value), this } ifNothing(_: () => any): this { return this } filter(pred: (value: T) => value is U): Maybe filter(pred: (value: T) => boolean): Maybe filter(pred: (value: T) => boolean) { return pred(this.__value) ? just(this.__value) : nothing } 'fantasy-land/equals' = this.equals 'fantasy-land/map' = this.map 'fantasy-land/ap' = this.ap 'fantasy-land/alt' = this.alt 'fantasy-land/chain' = this.chain 'fantasy-land/reduce' = this.reduce 'fantasy-land/extend' = this.extend 'fantasy-land/filter' = this.filter } Just.prototype.constructor = Maybe as any class Nothing implements Maybe { private __value!: never isJust() { return false } isNothing() { return true } inspect(): string { return 'Nothing' } toString(): string { return this.inspect() } toJSON(): never { return this.__value } equals(other: Maybe): boolean { return this.extract() === other.extract() } map(_: (value: never) => U): Maybe { return nothing } ap(_: Maybe<(value: never) => U>): Maybe { return nothing } alt(other: Maybe): Maybe { return other } altLazy(other: () => Maybe): Maybe { return other() } chain(_: (value: never) => Maybe): Maybe { return nothing } chainNullable(_: (value: never) => U | undefined | null | void): Maybe { return nothing } join(this: Maybe>): Maybe { return nothing } reduce(_: (accumulator: U, value: never) => U, initialValue: U): U { return initialValue } extend(_: (value: Maybe) => U): Maybe { return nothing } unsafeCoerce(): T { throw new Error('Maybe#unsafeCoerce was ran on a Nothing') } caseOf(patterns: MaybePatterns): U { return '_' in patterns ? patterns._() : patterns.Nothing() } orDefault(defaultValue: T): T { return defaultValue } orDefaultLazy(getDefaultValue: () => T): T { return getDefaultValue() } toList(): T[] { return [] } mapOrDefault(_: (value: never) => U, defaultValue: U): U { return defaultValue } extract(): this extends AlwaysJust ? never : ExtractMaybe { return undefined as this extends AlwaysJust ? never : ExtractMaybe } extractNullable(): this extends AlwaysJust ? never : ExtractMaybe { return null as this extends AlwaysJust ? never : ExtractMaybe } toEither(left: L): Either { return Left(left) } ifJust(_: (value: never) => any): this { return this } ifNothing(effect: () => any): this { return effect(), this } filter(_: (value: never) => boolean): Maybe { return nothing } 'fantasy-land/equals' = this.equals 'fantasy-land/map' = this.map 'fantasy-land/ap' = this.ap 'fantasy-land/alt' = this.alt 'fantasy-land/chain' = this.chain 'fantasy-land/reduce' = this.reduce 'fantasy-land/extend' = this.extend 'fantasy-land/filter' = this.filter } Nothing.prototype.constructor = Maybe as any /** Constructs a Just. Represents an optional value that exists */ const just = (value: T): Maybe => new Just(value) /** Represents a missing value, you can think of it as a smart 'null' */ const nothing = new Nothing() export { just as Just, nothing as Nothing }