Skip to content

Commit

Permalink
feat: maybe many features
Browse files Browse the repository at this point in the history
  • Loading branch information
Hfutsora committed May 26, 2022
1 parent 393cb86 commit d27e15e
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 10 deletions.
93 changes: 90 additions & 3 deletions src/Maybe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Predicate } from './Predicate'
import { Lazy } from './function'
import { Lazy, constNull, identity, constUndefined } from './function'

export interface None {
readonly _tag: 'None'
Expand All @@ -15,6 +15,8 @@ export type Maybe<A> = None | Some<A>
/**
* Returns whether the maybe is `None` or not.
*
* @example
*
* ```ts
* assert.deepStrictEqual(isNone(none), true)
* assert.deepStrictEqual(isNone(some(1)), false)
Expand All @@ -25,6 +27,8 @@ export const isNone = <A>(m: Maybe<A>): m is None => m._tag === 'None'
/**
* Returns whether the maybe is `Some` or not.
*
* @example
*
* ```ts
* assert.deepStrictEqual(isSome(some(1)), true)
* assert.deepStrictEqual(isSome(none), false)
Expand Down Expand Up @@ -59,12 +63,95 @@ export function fromPredicate<A>(predicate: Predicate<A>): (a: A) => Maybe<A> {
return (a) => (predicate(a) ? some(a) : none)
}

/**
* instance `map` operation.
*/
export const map = <A, B>(f: (a: A) => B) => (fa: Maybe<A>): Maybe<B> =>
isNone(fa) ? none : some(f(fa.value))

/**
* instance `of` operation
*/
export const of = some

/**
* Returns the onNone default value if the `Maybe` is `None`, otherwise returns the onSome function result with `Maybe`.
*
* @example
*
* ```ts
* const f = pipe(
* match(() => 0, (n: number) => n + 1)
* )
*
* assert.deepStrictEqual(f(some(1)), 2)
* assert.deepStrictEqual(f(none), 1)
* ```
*/
export const match = <B, A, C>(onNone: Lazy<B>, onSome: (a: A) => C) => (ma: Maybe<A>): B | C =>
isNone(ma) ? onNone() : onSome(ma.value)

/**
* Composes computations in sequence
*
* @example
*
* ```ts
* const f = pipe(
* chain((n: number) => some(n + 1)
* )
*
* assert.deepStrictEqual(f(some(1)), some(2))
* assert.deepStrictEqual(f(none), none)
* ```
*/
export const chain = <A, B>(f: (a: A) => Maybe<B>) => (ma: Maybe<A>): Maybe<B> =>
isNone(ma) ? none : f(ma.value)

/**
* Extracts the value out of `Maybe`, if it exists. Otherwise returns `null`.
*
* @example
*
* ```ts
* const f = pipe(
* toNullable
* )
*
* assert.deepStrictEqual(f(some(1)), 1)
* assert.deepStrictEqual(f(none), null)
* ```
*/
export const toNullable: <A>(ma: Maybe<A>) => A | null = match(constNull, identity)

/**
* Extracts the value out of `Maybe`, if it exists. Otherwise returns `undefined`.
*
* @example
*
* ```ts
* const f = pipe(
* toUndefined
* )
*
* assert.deepStrictEqual(f(some(1)), 1)
* assert.deepStrictEqual(f(none), undefined)
* ```
*/
export const toUndefined: <A>(ma: Maybe<A>) => A | undefined = match(constUndefined, identity)

/**
* Extracts the value of `Maybe`, if it exists. Otherwise return then default onNone value.
*
* @example
*
* ```ts
* assert.deepStrictEqual(getOrElse(() => 0)(some(1)), 1)
* assert.deepStrictEqual(getOrElse(() => 0)(none)), 0)
* const f = pipe(
* getOrElse(() => 0)
* )
*
* assert.deepStrictEqual(f(some(1)), 1)
* assert.deepStrictEqual(f(none)), 0)
* ```
*/
export const getOrElse = <A>(onNone: Lazy<A>) => <B>(ma: Maybe<B>): A | B => isNone(ma) ? onNone() : ma.value
25 changes: 25 additions & 0 deletions src/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@ export type Lazy<A> = () => A
*/
export const identity = <A>(a: A): A => a

/**
* A thunk returns self.
*/
export const constant = <A>(a: A): Lazy<A> => () => a
/**
* A thunk returns always `true`.
*/
export const constTrue: Lazy<boolean> = constant(true)
/**
* A thunk returns always `false`.
*/
export const constFalse: Lazy<boolean> = constant(false)
/**
* A thunk returns always `null`.
*/
export const constNull: Lazy<null> = constant(null)
/**
* A thunk returns always `undefined`.
*/
export const constUndefined: Lazy<undefined> = constant(void 0)
/**
* A thunk returns always `void`.
*/
export const constVoid = constUndefined


type PipeFn<A, B> = (a: A) => B

Expand Down
53 changes: 46 additions & 7 deletions test/Maybe.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isSome, some, isNone, none, fromPredicate, getOrElse } from '../src/Maybe'
import { isSome, some, isNone, none, fromPredicate, getOrElse, of, map, chain, match } from '../src/Maybe'
import { pipe } from '../src/function'

test('isSome', () => {
expect(isSome(some(1))).toBeTruthy()
Expand All @@ -11,14 +12,52 @@ test('isNone', () => {
})

test('fromPredicate', () => {
const getMaybe = fromPredicate((n: number) => n > 0)
const f = pipe(
fromPredicate((n: number) => n > 0)
)

expect(getMaybe(1)).toEqual(some(1))
expect(getMaybe(0)).toEqual(none)
expect(f(1)).toEqual(some(1))
expect(f(0)).toEqual(none)
})


test('getOrElse', () => {
expect(getOrElse(() => 0)(some(1))).toBe(1)
expect(getOrElse(() => 0)(none)).toBe(0)
const f = pipe(
getOrElse(() => 0)
)

expect(f(some(1))).toBe(1)
expect(f(none)).toBe(0)
})

test('of', () => {
expect(of(1)).toEqual(some(1))
})

test('map', () => {
const f = pipe(
map((n: number) => n + 1),
getOrElse(() => 0)
)
expect(f(some(1))).toEqual(2)
expect(f(none)).toEqual(0)
})

test('chain', () => {
const f = pipe(
chain((n: number) => n > 0 ? some(n) : none),
getOrElse(() => 0)
)

expect(f(some(1))).toEqual(1)
expect(f(some(-1))).toEqual(0)
expect(f(none)).toEqual(0)
})

test('match', () => {
const f = pipe(
match(() => 0, (n: number) => n + 1)
)

expect(f(some(1))).toBe(2)
expect(f(none)).toBe(0)
})

0 comments on commit d27e15e

Please sign in to comment.