Skip to content

Commit 750510c

Browse files
feat: introduce result monad (#67)
closes #65
1 parent 2b9c412 commit 750510c

File tree

13 files changed

+281
-19
lines changed

13 files changed

+281
-19
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@
3131
</a>
3232
</p>
3333

34-
**typescript-monads** helps you write safer code by using abstractions over dubious program state and control flow.
34+
**typescript-monads** helps you write safer code by using abstractions over messy control flow and state.
3535

36-
# Getting Started
36+
# Installation
37+
You can use this library in the browser, node, or a bundler
3738

3839
## Node or as a module
3940
```bash
@@ -45,7 +46,7 @@ npm install typescript-monads
4546
<head>
4647
<script src="https://unpkg.com/typescript-monads"></script>
4748
<!-- or use a specific version to avoid a redirect -->
48-
<script src="https://unpkg.com/typescript-monads@3.5.3/index.js"></script>
49+
<script src="https://unpkg.com/typescript-monads@3.8.0/index.min.js"></script>
4950
</head>
5051
```
5152

@@ -54,7 +55,7 @@ var someRemoteValue;
5455
typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
5556
```
5657

57-
# Usage
58+
# Example Usage
5859

5960
* [Maybe](#maybe)
6061
* [Either](#either)
@@ -92,7 +93,6 @@ maybe(process.env.DB_URL)
9293
})
9394
```
9495

95-
9696
# Either
9797
TODO
9898

src/interfaces/maybe.interface.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IMonad } from "./monad.interface"
1+
import { IMonad } from './monad.interface'
22

33
/**
44
* Define a contract to unwrap Maybe object

src/interfaces/result.interface.ts

Whitespace-only changes.

src/monads/either.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IEither, IEitherPattern } from "../interfaces"
1+
import { IEither, IEitherPattern } from '../interfaces'
22

33
const exists = <T>(t: T) => t !== null && t !== undefined
44
const bothExist = <L, R>(left?: L) => (right?: R) => exists(left) && exists(right)

src/monads/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
export { monad } from './monad'
2-
export { maybe } from './maybe'
3-
export { either } from './either'
4-
export { reader } from './reader'
1+
export * from './monad'
2+
export * from './maybe'
3+
export * from './either'
4+
export * from './reader'
5+
export * from './result'

src/monads/maybe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IMaybe, IMaybePattern } from "../interfaces"
1+
import { IMaybe, IMaybePattern } from '../interfaces'
22

33
const isEmpty = <T>(value: T) => value === null || value === undefined
44
const isNotEmpty = <T>(value: T) => !isEmpty(value)

src/monads/monad.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mapping, IMonad } from "../interfaces"
1+
import { mapping, IMonad } from '../interfaces'
22

33
// tslint:disable:readonly-array
44
export const monad = <T>(x: T, ...args: any[]): IMonad<T> => {

src/monads/reader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { IReader } from "../interfaces"
1+
import { IReader } from '../interfaces'
22

33
// tslint:disable:no-this
44
export const reader = <E, A>(fn: (config: E) => A): IReader<E, A> => {

src/monads/result.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { IMaybe } from '../interfaces'
2+
import { maybe } from './maybe'
3+
4+
const returnTrue = () => true
5+
const returnFalse = () => false
6+
const returnValue = <T>(val: T) => () => val
7+
const returnMaybe = <T>(val: T) => () => maybe<T>(val)
8+
const throwReferenceError = (message: string) => () => { throw new ReferenceError(message) }
9+
10+
type Predicate = () => boolean
11+
12+
export interface IResultMatchPattern<T, E, U> {
13+
readonly ok: (val: T) => U
14+
readonly fail: (val: E) => U
15+
}
16+
17+
export interface IResult<T, E> {
18+
isOk(): boolean
19+
isFail(): boolean
20+
maybeOk(): IMaybe<T>
21+
maybeFail(): IMaybe<E>
22+
unwrap(): T | never
23+
unwrapOr(opt: T): T
24+
unwrapFail(): E | never
25+
match<M>(fn: IResultMatchPattern<T, E, M>): M
26+
map<M>(fn: (val: T) => M): IResult<M, E>
27+
mapFail<M>(fn: (err: E) => M): IResult<T, M>
28+
flatMap<M>(fn: (val: T) => IResult<M, E>): IResult<M, E>
29+
}
30+
31+
export interface IResultOk<T, E = never> extends IResult<T, E> {
32+
unwrap(): T
33+
unwrapOr(opt: T): T
34+
unwrapFail(): never
35+
match<M>(fn: IResultMatchPattern<T, never, M>): M
36+
map<M>(fn: (val: T) => M): IResultOk<M, never>
37+
mapFail<M>(fn: (err: E) => M): IResultOk<T, never>
38+
}
39+
40+
export interface IResultFail<T, E> extends IResult<T, E> {
41+
unwrap(): never
42+
unwrapOr(opt: T): T
43+
unwrapFail(): E
44+
match<M>(fn: IResultMatchPattern<never, E, M>): M
45+
map<M>(fn: (val: T) => M): IResultFail<never, E>
46+
mapFail<M>(fn: (err: E) => M): IResultFail<never, M>
47+
flatMap<M>(fn: (val: T) => IResult<M, E>): IResultFail<never, E>
48+
}
49+
50+
export const ok = <T, E = never>(val: T): IResultOk<T, E> => {
51+
return {
52+
isOk: returnTrue,
53+
isFail: returnFalse,
54+
maybeOk: returnMaybe(val),
55+
maybeFail: maybe,
56+
unwrap: returnValue(val),
57+
unwrapOr: _ => val,
58+
unwrapFail: throwReferenceError('Cannot unwrap a success'),
59+
map: <M>(fn: (val: T) => M) => ok(fn(val)),
60+
mapFail: <M>(_: (err: E) => M) => ok(val),
61+
flatMap: <M>(fn: (val: T) => IResult<M, E>) => fn(val),
62+
match: <M>(fn: IResultMatchPattern<T, E, M>) => fn.ok(val)
63+
}
64+
}
65+
66+
export const fail = <T, E>(err: E): IResultFail<T, E> => {
67+
return {
68+
isOk: returnFalse,
69+
isFail: returnTrue,
70+
maybeOk: maybe,
71+
maybeFail: returnMaybe(err),
72+
unwrap: throwReferenceError('Cannot unwrap a failure'),
73+
unwrapOr: opt => opt,
74+
unwrapFail: returnValue(err),
75+
map: <M>(_: (val: T) => M) => fail(err),
76+
mapFail: <M>(fn: (err: E) => M) => fail(fn(err)),
77+
flatMap: <M>(_: (val: T) => IResult<M, E>) => fail(err),
78+
match: <M>(fn: IResultMatchPattern<T, E, M>) => fn.fail(err)
79+
}
80+
}
81+
82+
/**
83+
* Utility function to quickly create ok/fail pairs.
84+
*/
85+
export const result = <T, E>(predicate: Predicate, okValue: T, failValue: E): IResult<T, E> =>
86+
predicate()
87+
? ok<T, E>(okValue)
88+
: fail<T, E>(failValue)
89+
90+
/**
91+
* Utility function to quickly create ok/fail pairs, curried variant.
92+
*/
93+
export const curriedResult =
94+
<T, E>(predicate: Predicate) =>
95+
(okValue: T) =>
96+
(failValue: E): IResult<T, E> =>
97+
result(predicate, okValue, failValue)

src/util/maybe-env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { reader, maybe } from ".."
1+
import { reader, maybe } from '..'
22

33
export interface GetFromEnvironmentReader {
44
readEnv(key: string): string | undefined

0 commit comments

Comments
 (0)