Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions src/interfaces/maybe.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ export interface IMaybePattern<TIn, TOut> {
* Abstraction for handling possibility of undefined values
*/
export interface IMaybe<T> extends IMonad<T> {

// tslint:disable-next-line:readonly-array
of(x?: T, ...args: any[]): IMaybe<T>

/**
* Unwrap a Maybe with a default value
*/
valueOr(val: NonNullable<T>): T

/**
* Unwrap a Maybe with its value or return undefined if its empty
*/
valueOrUndefined(): T | undefined

/**
* Unwrap a Maybe with a default computed value
*/
Expand All @@ -33,12 +42,12 @@ export interface IMaybe<T> extends IMonad<T> {
* Execute functions with side-effects.
*/
tap(val: Partial<IMaybePattern<T, void>>): 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.
*/
Expand All @@ -50,17 +59,18 @@ export interface IMaybe<T> extends IMonad<T> {
match<R>(pattern: IMaybePattern<T, R>): R

/**
* Combine multiple maybe
* Map output of non-empty data to a new value
*/
map<R>(f: (t: T) => R): IMaybe<R>

/**
* Combine multiple maybe
* Combine multiple Maybe
*/
flatMap<R>(f: (t: T) => IMaybe<R>): IMaybe<R>

// tslint:disable-next-line:readonly-array
of(x?: T, ...args: any[]): IMaybe<T>

/**
* Apply a predicate which if met, continues the Maybe chain,
* otherwise return an empty Maybe
*/
filter(fn: (t: T) => boolean): IMaybe<T>
}
2 changes: 2 additions & 0 deletions src/monads/maybe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IMaybe, IMaybePattern } from "../interfaces"
const isEmpty = <T>(value: T) => value === null || value === undefined
const isNotEmpty = <T>(value: T) => !isEmpty(value)
const valueOr = <T>(value?: T) => (val: NonNullable<T>) => isEmpty(value) ? val : value as NonNullable<T>
const valueOrUndefined = <T>(value?: T) => () => isEmpty(value) ? undefined : value as NonNullable<T>
const valueOrCompute = <T>(value?: T) => (fn: () => NonNullable<T>) => isEmpty(value) ? fn() : value as NonNullable<T>
const tap = <T>(value?: T) => (obj: Partial<IMaybePattern<T, void>>) => isEmpty(value) ? obj.none && obj.none() : obj.some && obj.some(value as NonNullable<T>)
const tapNone = <T>(value?: T) => (fn: () => void) => (isEmpty(value)) && fn()
Expand All @@ -15,6 +16,7 @@ export const maybe = <T>(value?: T): IMaybe<NonNullable<T>> => {
return {
of: maybe,
valueOr: valueOr(value),
valueOrUndefined: valueOrUndefined(value),
valueOrCompute: valueOrCompute(value),
tap: tap(value),
tapNone: tapNone(value),
Expand Down
20 changes: 19 additions & 1 deletion test/monads/maybe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
})