diff --git a/src/interfaces/maybe.interface.ts b/src/interfaces/maybe.interface.ts index d540152..1832847 100644 --- a/src/interfaces/maybe.interface.ts +++ b/src/interfaces/maybe.interface.ts @@ -19,11 +19,20 @@ export interface IMaybePattern { * Abstraction for handling possibility of undefined values */ export interface IMaybe extends IMonad { + + // tslint:disable-next-line:readonly-array + of(x?: T, ...args: any[]): IMaybe + /** * Unwrap a Maybe with a default value */ valueOr(val: NonNullable): T + /** + * Unwrap a Maybe with its value or return undefined if its empty + */ + valueOrUndefined(): T | undefined + /** * Unwrap a Maybe with a default computed value */ @@ -33,12 +42,12 @@ export interface IMaybe extends IMonad { * Execute functions with side-effects. */ tap(val: Partial>): void - + /** * Execute a function with side-effects when maybe is a none. */ tapNone(f: () => void): void - + /** * Execute a function with side-effects when maybe is a some. */ @@ -50,17 +59,18 @@ export interface IMaybe extends IMonad { match(pattern: IMaybePattern): R /** - * Combine multiple maybe + * Map output of non-empty data to a new value */ map(f: (t: T) => R): IMaybe /** - * Combine multiple maybe + * Combine multiple Maybe */ flatMap(f: (t: T) => IMaybe): IMaybe - // tslint:disable-next-line:readonly-array - of(x?: T, ...args: any[]): IMaybe - + /** + * Apply a predicate which if met, continues the Maybe chain, + * otherwise return an empty Maybe + */ filter(fn: (t: T) => boolean): IMaybe } \ No newline at end of file diff --git a/src/monads/maybe.ts b/src/monads/maybe.ts index 5dde4a3..ba85bc0 100644 --- a/src/monads/maybe.ts +++ b/src/monads/maybe.ts @@ -3,6 +3,7 @@ import { IMaybe, IMaybePattern } from "../interfaces" const isEmpty = (value: T) => value === null || value === undefined const isNotEmpty = (value: T) => !isEmpty(value) const valueOr = (value?: T) => (val: NonNullable) => isEmpty(value) ? val : value as NonNullable +const valueOrUndefined = (value?: T) => () => isEmpty(value) ? undefined : value as NonNullable const valueOrCompute = (value?: T) => (fn: () => NonNullable) => isEmpty(value) ? fn() : value as NonNullable const tap = (value?: T) => (obj: Partial>) => isEmpty(value) ? obj.none && obj.none() : obj.some && obj.some(value as NonNullable) const tapNone = (value?: T) => (fn: () => void) => (isEmpty(value)) && fn() @@ -15,6 +16,7 @@ export const maybe = (value?: T): IMaybe> => { return { of: maybe, valueOr: valueOr(value), + valueOrUndefined: valueOrUndefined(value), valueOrCompute: valueOrCompute(value), tap: tap(value), tapNone: tapNone(value), diff --git a/test/monads/maybe.spec.ts b/test/monads/maybe.spec.ts index 4903c27..e1a8abb 100644 --- a/test/monads/maybe.spec.ts +++ b/test/monads/maybe.spec.ts @@ -328,5 +328,23 @@ describe('Maybe', () => { }) }) }) -}) + + + + describe('when returning a value or undefined', () => { + it('should handle "none" case', () => { + const sut = undefined as string | undefined + const maybeAString = maybe(sut).valueOrUndefined() + + expect(maybeAString).toBeUndefined() + }) + + it('should handle "some" case', () => { + const sut = 'actual input' as string | undefined + const maybeAString = maybe(sut).valueOrUndefined() + + expect(maybeAString).toEqual('actual input') + }) + }) +})