Skip to content
This repository has been archived by the owner on Apr 2, 2023. It is now read-only.

Commit

Permalink
feat: add Result type
Browse files Browse the repository at this point in the history
Also derived from Either
  • Loading branch information
tdreyno committed May 2, 2020
1 parent 1ba23f5 commit fb9a4cf
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 2 deletions.
8 changes: 6 additions & 2 deletions src/monads/Maybe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
isLeft,
isRight,
} from "./Either"
import { Result, fold as resultFold } from "./Result"

type Just<T> = Right<T>
type Nothing<T = unknown> = Left<null>
export type Just<T> = Right<T>
export type Nothing<T = unknown> = Left<null>
export type Maybe<T> = Nothing<T> | Just<T>

export const Just = <T>(value: T): Maybe<T> => Right(value)
Expand All @@ -35,6 +36,9 @@ export const cata = <T, U>(handlers: { Nothing: () => U; Just: (v: T) => U }) =>
export const fold = <T, U>(nothingFn: () => U, justFn: (v: T) => U) =>
fold_<null, T, U>(nothingFn, justFn) as (maybe: Maybe<T>) => U

export const fromResult = <A, B>(result: Result<A, B>) =>
resultFold(Nothing, Just)(result) as Maybe<B>

// Chain
export const chain = <T, U>(fn: (a: T) => Maybe<U>) =>
chain_(fn) as (maybe: Maybe<T>) => Maybe<U>
Expand Down
50 changes: 50 additions & 0 deletions src/monads/Result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { identity, pipe } from "../core/index"
import {
Right,
Left,
cata as cata_,
fold as fold_,
chain as chain_,
map as map_,
isLeft,
isRight,
} from "./Either"

export type Ok<B> = Right<B>
export type Err<A> = Left<A>
export type Result<A, B> = Err<A> | Ok<B>

export const Ok = <B, A = unknown>(value: B): Result<A, B> => Right(value)
export const Err = <A, B = unknown>(value: A): Result<A, B> => Left(value)

export const of = Ok

export const cata = <A, B, U>(handlers: {
Err: (a: A) => U
Ok: (v: B) => U
}) =>
cata_({
Left: handlers.Err,
Right: handlers.Ok,
}) as (result: Result<A, B>) => U

export const fold = <A, B, U>(errorFn: (a: A) => U, okFn: (b: B) => U) =>
fold_(errorFn, okFn) as (result: Result<A, B>) => U

// Chain
export const chain = <A, B, U>(fn: (a: B) => Result<A, U>) =>
chain_(fn) as (result: Result<A, B>) => Result<A, U>

// Functor
export const map = <A, B, U>(fn: (a: B) => U) =>
map_(fn) as (result: Result<A, B>) => Result<A, U>

// Error handling
export const orElse = <A, B>(fn: (a: A) => B) =>
pipe(fold<A, B, B>(fn, identity), (v: B) => Ok<B, A>(v))

export const isOk = <A, B>(result: Result<A, B>): result is Ok<B> =>
isRight(result)

export const isError = <A, B>(result: Result<A, B>): result is Err<A> =>
isLeft(result)
6 changes: 6 additions & 0 deletions src/monads/__tests__/Maybe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
orElse,
isJust,
isNothing,
fromResult,
} from "../Maybe"
import { Err, Ok } from "../Result"

describe("Maybe", () => {
test("Just", () => {
Expand Down Expand Up @@ -41,6 +43,10 @@ describe("Maybe", () => {
expect(fromNullable(null)).toEqual(Nothing()))
test("fromNullable(5)", () => expect(fromNullable(5)).toEqual(Just(5)))

test("fromResult(Err)", () =>
expect(fromResult(Err("whoops"))).toEqual(Nothing()))
test("fromResult(Ok)", () => expect(fromResult(Ok(5))).toEqual(Just(5)))

test("cata(Just)", () => {
const J = jest.fn()
const N = jest.fn()
Expand Down
74 changes: 74 additions & 0 deletions src/monads/__tests__/Result.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
Ok,
Err,
cata,
fold,
of,
map,
chain,
orElse,
isOk,
isError,
} from "../Result"

describe("Result", () => {
test("Ok", () => {
const O = jest.fn()
const E = jest.fn()

fold(E, O)(Ok(5))

expect(O).toBeCalledWith(5)
expect(E).not.toHaveBeenCalled()
})

test("Err", () => {
const O = jest.fn()
const E = jest.fn()

fold(E, O)(Err("whoops"))

expect(O).not.toHaveBeenCalled()
expect(E).toHaveBeenCalled()
})

test("of", () => expect(of(5)).toEqual(Ok(5)))

test("cata(Ok)", () => {
const O = jest.fn()
const E = jest.fn()

cata({ Ok: O, Err: E })(Ok(5))

expect(O).toBeCalledWith(5)
expect(E).not.toHaveBeenCalled()
})

test("cata(Err)", () => {
const O = jest.fn()
const E = jest.fn()

cata({ Ok: O, Err: E })(Err("whoops"))

expect(O).not.toHaveBeenCalled()
expect(E).toHaveBeenCalled()
})

test("map(Ok)", () =>
expect(map((v: number) => v * 2)(Ok(5))).toEqual(Ok(10)))
test("map(Err)", () =>
expect(map(jest.fn())(Err("whoops"))).toEqual(Err("whoops")))

test("chain(Ok)", () =>
expect(chain((v: number) => Ok(v * 2))(Ok(5))).toEqual(Ok(10)))
test("chain(Err)", () =>
expect(chain(jest.fn())(Err("whoops"))).toEqual(Err("whoops")))

test("orElse", () => expect(orElse(() => 10)(Err("whoops"))).toEqual(Ok(10)))

test("isOk(Ok)", () => expect(isOk(Ok(5))).toBe(true))
test("isOk(Err)", () => expect(isOk(Err("whoops"))).toBe(false))

test("isError(Ok)", () => expect(isError(Ok(5))).toBe(false))
test("isError(Err)", () => expect(isError(Err("whoops"))).toBe(true))
})

0 comments on commit fb9a4cf

Please sign in to comment.