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

Commit fb9a4cf

Browse files
committed
feat: add Result type
Also derived from Either
1 parent 1ba23f5 commit fb9a4cf

File tree

4 files changed

+136
-2
lines changed

4 files changed

+136
-2
lines changed

src/monads/Maybe.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
isLeft,
1010
isRight,
1111
} from "./Either"
12+
import { Result, fold as resultFold } from "./Result"
1213

13-
type Just<T> = Right<T>
14-
type Nothing<T = unknown> = Left<null>
14+
export type Just<T> = Right<T>
15+
export type Nothing<T = unknown> = Left<null>
1516
export type Maybe<T> = Nothing<T> | Just<T>
1617

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

39+
export const fromResult = <A, B>(result: Result<A, B>) =>
40+
resultFold(Nothing, Just)(result) as Maybe<B>
41+
3842
// Chain
3943
export const chain = <T, U>(fn: (a: T) => Maybe<U>) =>
4044
chain_(fn) as (maybe: Maybe<T>) => Maybe<U>

src/monads/Result.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { identity, pipe } from "../core/index"
2+
import {
3+
Right,
4+
Left,
5+
cata as cata_,
6+
fold as fold_,
7+
chain as chain_,
8+
map as map_,
9+
isLeft,
10+
isRight,
11+
} from "./Either"
12+
13+
export type Ok<B> = Right<B>
14+
export type Err<A> = Left<A>
15+
export type Result<A, B> = Err<A> | Ok<B>
16+
17+
export const Ok = <B, A = unknown>(value: B): Result<A, B> => Right(value)
18+
export const Err = <A, B = unknown>(value: A): Result<A, B> => Left(value)
19+
20+
export const of = Ok
21+
22+
export const cata = <A, B, U>(handlers: {
23+
Err: (a: A) => U
24+
Ok: (v: B) => U
25+
}) =>
26+
cata_({
27+
Left: handlers.Err,
28+
Right: handlers.Ok,
29+
}) as (result: Result<A, B>) => U
30+
31+
export const fold = <A, B, U>(errorFn: (a: A) => U, okFn: (b: B) => U) =>
32+
fold_(errorFn, okFn) as (result: Result<A, B>) => U
33+
34+
// Chain
35+
export const chain = <A, B, U>(fn: (a: B) => Result<A, U>) =>
36+
chain_(fn) as (result: Result<A, B>) => Result<A, U>
37+
38+
// Functor
39+
export const map = <A, B, U>(fn: (a: B) => U) =>
40+
map_(fn) as (result: Result<A, B>) => Result<A, U>
41+
42+
// Error handling
43+
export const orElse = <A, B>(fn: (a: A) => B) =>
44+
pipe(fold<A, B, B>(fn, identity), (v: B) => Ok<B, A>(v))
45+
46+
export const isOk = <A, B>(result: Result<A, B>): result is Ok<B> =>
47+
isRight(result)
48+
49+
export const isError = <A, B>(result: Result<A, B>): result is Err<A> =>
50+
isLeft(result)

src/monads/__tests__/Maybe.spec.ts

+6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import {
1111
orElse,
1212
isJust,
1313
isNothing,
14+
fromResult,
1415
} from "../Maybe"
16+
import { Err, Ok } from "../Result"
1517

1618
describe("Maybe", () => {
1719
test("Just", () => {
@@ -41,6 +43,10 @@ describe("Maybe", () => {
4143
expect(fromNullable(null)).toEqual(Nothing()))
4244
test("fromNullable(5)", () => expect(fromNullable(5)).toEqual(Just(5)))
4345

46+
test("fromResult(Err)", () =>
47+
expect(fromResult(Err("whoops"))).toEqual(Nothing()))
48+
test("fromResult(Ok)", () => expect(fromResult(Ok(5))).toEqual(Just(5)))
49+
4450
test("cata(Just)", () => {
4551
const J = jest.fn()
4652
const N = jest.fn()

src/monads/__tests__/Result.spec.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {
2+
Ok,
3+
Err,
4+
cata,
5+
fold,
6+
of,
7+
map,
8+
chain,
9+
orElse,
10+
isOk,
11+
isError,
12+
} from "../Result"
13+
14+
describe("Result", () => {
15+
test("Ok", () => {
16+
const O = jest.fn()
17+
const E = jest.fn()
18+
19+
fold(E, O)(Ok(5))
20+
21+
expect(O).toBeCalledWith(5)
22+
expect(E).not.toHaveBeenCalled()
23+
})
24+
25+
test("Err", () => {
26+
const O = jest.fn()
27+
const E = jest.fn()
28+
29+
fold(E, O)(Err("whoops"))
30+
31+
expect(O).not.toHaveBeenCalled()
32+
expect(E).toHaveBeenCalled()
33+
})
34+
35+
test("of", () => expect(of(5)).toEqual(Ok(5)))
36+
37+
test("cata(Ok)", () => {
38+
const O = jest.fn()
39+
const E = jest.fn()
40+
41+
cata({ Ok: O, Err: E })(Ok(5))
42+
43+
expect(O).toBeCalledWith(5)
44+
expect(E).not.toHaveBeenCalled()
45+
})
46+
47+
test("cata(Err)", () => {
48+
const O = jest.fn()
49+
const E = jest.fn()
50+
51+
cata({ Ok: O, Err: E })(Err("whoops"))
52+
53+
expect(O).not.toHaveBeenCalled()
54+
expect(E).toHaveBeenCalled()
55+
})
56+
57+
test("map(Ok)", () =>
58+
expect(map((v: number) => v * 2)(Ok(5))).toEqual(Ok(10)))
59+
test("map(Err)", () =>
60+
expect(map(jest.fn())(Err("whoops"))).toEqual(Err("whoops")))
61+
62+
test("chain(Ok)", () =>
63+
expect(chain((v: number) => Ok(v * 2))(Ok(5))).toEqual(Ok(10)))
64+
test("chain(Err)", () =>
65+
expect(chain(jest.fn())(Err("whoops"))).toEqual(Err("whoops")))
66+
67+
test("orElse", () => expect(orElse(() => 10)(Err("whoops"))).toEqual(Ok(10)))
68+
69+
test("isOk(Ok)", () => expect(isOk(Ok(5))).toBe(true))
70+
test("isOk(Err)", () => expect(isOk(Err("whoops"))).toBe(false))
71+
72+
test("isError(Ok)", () => expect(isError(Ok(5))).toBe(false))
73+
test("isError(Err)", () => expect(isError(Err("whoops"))).toBe(true))
74+
})

0 commit comments

Comments
 (0)