Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
</a>
</p>

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

# Getting Started
# Installation
You can use this library in the browser, node, or a bundler

## Node or as a module
```bash
Expand All @@ -45,7 +46,7 @@ npm install typescript-monads
<head>
<script src="https://unpkg.com/typescript-monads"></script>
<!-- or use a specific version to avoid a redirect -->
<script src="https://unpkg.com/typescript-monads@3.5.3/index.js"></script>
<script src="https://unpkg.com/typescript-monads@3.8.0/index.min.js"></script>
</head>
```

Expand All @@ -54,7 +55,7 @@ var someRemoteValue;
typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
```

# Usage
# Example Usage

* [Maybe](#maybe)
* [Either](#either)
Expand Down Expand Up @@ -92,7 +93,6 @@ maybe(process.env.DB_URL)
})
```


# Either
TODO

Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/maybe.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMonad } from "./monad.interface"
import { IMonad } from './monad.interface'

/**
* Define a contract to unwrap Maybe object
Expand Down
Empty file.
2 changes: 1 addition & 1 deletion src/monads/either.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IEither, IEitherPattern } from "../interfaces"
import { IEither, IEitherPattern } from '../interfaces'

const exists = <T>(t: T) => t !== null && t !== undefined
const bothExist = <L, R>(left?: L) => (right?: R) => exists(left) && exists(right)
Expand Down
9 changes: 5 additions & 4 deletions src/monads/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { monad } from './monad'
export { maybe } from './maybe'
export { either } from './either'
export { reader } from './reader'
export * from './monad'
export * from './maybe'
export * from './either'
export * from './reader'
export * from './result'
2 changes: 1 addition & 1 deletion src/monads/maybe.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMaybe, IMaybePattern } from "../interfaces"
import { IMaybe, IMaybePattern } from '../interfaces'

const isEmpty = <T>(value: T) => value === null || value === undefined
const isNotEmpty = <T>(value: T) => !isEmpty(value)
Expand Down
2 changes: 1 addition & 1 deletion src/monads/monad.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mapping, IMonad } from "../interfaces"
import { mapping, IMonad } from '../interfaces'

// tslint:disable:readonly-array
export const monad = <T>(x: T, ...args: any[]): IMonad<T> => {
Expand Down
2 changes: 1 addition & 1 deletion src/monads/reader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IReader } from "../interfaces"
import { IReader } from '../interfaces'

// tslint:disable:no-this
export const reader = <E, A>(fn: (config: E) => A): IReader<E, A> => {
Expand Down
97 changes: 97 additions & 0 deletions src/monads/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { IMaybe } from '../interfaces'
import { maybe } from './maybe'

const returnTrue = () => true
const returnFalse = () => false
const returnValue = <T>(val: T) => () => val
const returnMaybe = <T>(val: T) => () => maybe<T>(val)
const throwReferenceError = (message: string) => () => { throw new ReferenceError(message) }

type Predicate = () => boolean

export interface IResultMatchPattern<T, E, U> {
readonly ok: (val: T) => U
readonly fail: (val: E) => U
}

export interface IResult<T, E> {
isOk(): boolean
isFail(): boolean
maybeOk(): IMaybe<T>
maybeFail(): IMaybe<E>
unwrap(): T | never
unwrapOr(opt: T): T
unwrapFail(): E | never
match<M>(fn: IResultMatchPattern<T, E, M>): M
map<M>(fn: (val: T) => M): IResult<M, E>
mapFail<M>(fn: (err: E) => M): IResult<T, M>
flatMap<M>(fn: (val: T) => IResult<M, E>): IResult<M, E>
}

export interface IResultOk<T, E = never> extends IResult<T, E> {
unwrap(): T
unwrapOr(opt: T): T
unwrapFail(): never
match<M>(fn: IResultMatchPattern<T, never, M>): M
map<M>(fn: (val: T) => M): IResultOk<M, never>
mapFail<M>(fn: (err: E) => M): IResultOk<T, never>
}

export interface IResultFail<T, E> extends IResult<T, E> {
unwrap(): never
unwrapOr(opt: T): T
unwrapFail(): E
match<M>(fn: IResultMatchPattern<never, E, M>): M
map<M>(fn: (val: T) => M): IResultFail<never, E>
mapFail<M>(fn: (err: E) => M): IResultFail<never, M>
flatMap<M>(fn: (val: T) => IResult<M, E>): IResultFail<never, E>
}

export const ok = <T, E = never>(val: T): IResultOk<T, E> => {
return {
isOk: returnTrue,
isFail: returnFalse,
maybeOk: returnMaybe(val),
maybeFail: maybe,
unwrap: returnValue(val),
unwrapOr: _ => val,
unwrapFail: throwReferenceError('Cannot unwrap a success'),
map: <M>(fn: (val: T) => M) => ok(fn(val)),
mapFail: <M>(_: (err: E) => M) => ok(val),
flatMap: <M>(fn: (val: T) => IResult<M, E>) => fn(val),
match: <M>(fn: IResultMatchPattern<T, E, M>) => fn.ok(val)
}
}

export const fail = <T, E>(err: E): IResultFail<T, E> => {
return {
isOk: returnFalse,
isFail: returnTrue,
maybeOk: maybe,
maybeFail: returnMaybe(err),
unwrap: throwReferenceError('Cannot unwrap a failure'),
unwrapOr: opt => opt,
unwrapFail: returnValue(err),
map: <M>(_: (val: T) => M) => fail(err),
mapFail: <M>(fn: (err: E) => M) => fail(fn(err)),
flatMap: <M>(_: (val: T) => IResult<M, E>) => fail(err),
match: <M>(fn: IResultMatchPattern<T, E, M>) => fn.fail(err)
}
}

/**
* Utility function to quickly create ok/fail pairs.
*/
export const result = <T, E>(predicate: Predicate, okValue: T, failValue: E): IResult<T, E> =>
predicate()
? ok<T, E>(okValue)
: fail<T, E>(failValue)

/**
* Utility function to quickly create ok/fail pairs, curried variant.
*/
export const curriedResult =
<T, E>(predicate: Predicate) =>
(okValue: T) =>
(failValue: E): IResult<T, E> =>
result(predicate, okValue, failValue)
2 changes: 1 addition & 1 deletion src/util/maybe-env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { reader, maybe } from ".."
import { reader, maybe } from '..'

export interface GetFromEnvironmentReader {
readEnv(key: string): string | undefined
Expand Down
5 changes: 2 additions & 3 deletions test/interfaces/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import '../../src/interfaces'

describe('', () => {
it('should', () => {
describe('Interfaces', () => {
it('should cover interfaces', () => {
// stubbed just to get coverage up from interfaces
// which are not used
})
})
164 changes: 164 additions & 0 deletions test/monads/result.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { ok, fail, result, curriedResult } from '../../src/monads'

describe('result', () => {
describe('ok', () => {
it('should return true when "isOk" invoked on a success path', () => {
expect(ok(1).isOk()).toEqual(true)
})

it('should return false when "isFail" invoked on a success path', () => {
expect(ok(1).isFail()).toEqual(false)
})

it('should unwrap', () => {
expect(ok(1).unwrap()).toEqual(1)
expect(ok('Test').unwrap()).toEqual('Test')
})

it('should return proper value when "unwrapOr" is applied', () => {
expect(ok(1).unwrapOr(25)).toEqual(1)
expect(ok('Test').unwrapOr('Some Other')).toEqual('Test')
})

it('should throw an exception whe "unwrapOrFail" called on an ok value', () => {
expect(() => {
ok(1).unwrapFail()
}).toThrowError()
})

it('should ...', () => {
const _sut = ok('Test')
.maybeOk()
.valueOr('Some Other')

expect(_sut).toEqual('Test')
})

it('should ...', () => {
const _sut = ok('Test')
.maybeFail()
.valueOrUndefined()

expect(_sut).toEqual(undefined)
})

it('should map function', () => {
const sut = ok(1)
.map(b => b.toString())
.unwrap()
expect(sut).toEqual('1')
})

it('should not mapFail', () => {
const sut = ok(1)
.mapFail(b => '')
.unwrap()
expect(sut).toEqual(1)
})

it('should flatMap', () => {
const sut = ok(1)
.flatMap(a => ok(a.toString()))
.unwrap()

expect(sut).toEqual('1')
})

it('should match', () => {
const sut = ok(1)
.match({
fail: _ => 2,
ok: val => val
})

expect(sut).toEqual(1)
})
})

describe('fail', () => {
it('should return false when "isOk" invoked', () => {
expect(fail(1).isOk()).toEqual(false)
})

it('should return true when "isFail" invoked', () => {
expect(fail(1).isFail()).toEqual(true)
})

it('should return empty maybe when "maybeOk" is invoked', () => {
const _sut = fail('Test')
.maybeOk()
.valueOr('Some Other1')

expect(_sut).toEqual('Some Other1')
})

it('should return fail object when "maybeFail" is invoked', () => {
const _sut = fail('Test')
.maybeFail()
.valueOr('Some Other2')

expect(_sut).toEqual('Test')
})

it('should throw an exception on "unwrap"', () => {
expect(() => { fail(1).unwrap() }).toThrowError()
})

it('should return fail object on "unwrapFail"', () => {
expect(fail('123').unwrapFail()).toEqual('123')
})

it('should return input object on "unwrapOr"', () => {
expect(fail('123').unwrapOr('456')).toEqual('456')
})

it('should not map', () => {
const sut = fail(1)
.map(b => b.toString())
.unwrapFail()
expect(sut).toEqual(1)
})

it('should mapFail', () => {
const sut = fail(1)
.mapFail(b => b.toString())
.unwrapFail()
expect(sut).toEqual('1')
})

it('should not flatMap', () => {
const sut = fail(1)
.flatMap(a => ok(a.toString()))
.unwrapFail()

expect(sut).toEqual(1)
})

it('should match', () => {
const sut = fail(1)
.match({
fail: _ => 2,
ok: val => val
})

expect(sut).toEqual(2)
})
})

describe('result', () => {
it('should return failure when predicate yields false', () => {
const sut = result(() => 1 + 1 === 3, true, 'FAILURE!')
expect(sut.isFail()).toEqual(true)
})

it('should return ok when predicate yields true', () => {
const sut = result(() => 1 + 1 === 2, true, 'FAILURE!')
expect(sut.isOk()).toEqual(true)
})

it('should return curried', () => {
const sut = curriedResult(() => 1 + 1 === 2)(true)('FAILURE!')
expect(sut.isOk()).toEqual(true)
})
})
})
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"no-this": true,
"no-class": true,
"no-expression-statement": false,
"no-if-statement": true
"no-if-statement": true,
"quotemark": [true, "single"]
}
}