diff --git a/src/monads/Maybe.ts b/src/monads/Maybe.ts index a672830..55b687b 100644 --- a/src/monads/Maybe.ts +++ b/src/monads/Maybe.ts @@ -9,9 +9,10 @@ import { isLeft, isRight, } from "./Either" +import { Result, fold as resultFold } from "./Result" -type Just = Right -type Nothing = Left +export type Just = Right +export type Nothing = Left export type Maybe = Nothing | Just export const Just = (value: T): Maybe => Right(value) @@ -35,6 +36,9 @@ export const cata = (handlers: { Nothing: () => U; Just: (v: T) => U }) => export const fold = (nothingFn: () => U, justFn: (v: T) => U) => fold_(nothingFn, justFn) as (maybe: Maybe) => U +export const fromResult = (result: Result) => + resultFold(Nothing, Just)(result) as Maybe + // Chain export const chain = (fn: (a: T) => Maybe) => chain_(fn) as (maybe: Maybe) => Maybe diff --git a/src/monads/Result.ts b/src/monads/Result.ts new file mode 100644 index 0000000..e462a2a --- /dev/null +++ b/src/monads/Result.ts @@ -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 = Right +export type Err = Left +export type Result = Err | Ok + +export const Ok = (value: B): Result => Right(value) +export const Err = (value: A): Result => Left(value) + +export const of = Ok + +export const cata = (handlers: { + Err: (a: A) => U + Ok: (v: B) => U +}) => + cata_({ + Left: handlers.Err, + Right: handlers.Ok, + }) as (result: Result) => U + +export const fold = (errorFn: (a: A) => U, okFn: (b: B) => U) => + fold_(errorFn, okFn) as (result: Result) => U + +// Chain +export const chain = (fn: (a: B) => Result) => + chain_(fn) as (result: Result) => Result + +// Functor +export const map = (fn: (a: B) => U) => + map_(fn) as (result: Result) => Result + +// Error handling +export const orElse = (fn: (a: A) => B) => + pipe(fold(fn, identity), (v: B) => Ok(v)) + +export const isOk = (result: Result): result is Ok => + isRight(result) + +export const isError = (result: Result): result is Err => + isLeft(result) diff --git a/src/monads/__tests__/Maybe.spec.ts b/src/monads/__tests__/Maybe.spec.ts index b9268f4..5466804 100644 --- a/src/monads/__tests__/Maybe.spec.ts +++ b/src/monads/__tests__/Maybe.spec.ts @@ -11,7 +11,9 @@ import { orElse, isJust, isNothing, + fromResult, } from "../Maybe" +import { Err, Ok } from "../Result" describe("Maybe", () => { test("Just", () => { @@ -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() diff --git a/src/monads/__tests__/Result.spec.ts b/src/monads/__tests__/Result.spec.ts new file mode 100644 index 0000000..9873b98 --- /dev/null +++ b/src/monads/__tests__/Result.spec.ts @@ -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)) +})