diff --git a/README.md b/README.md
index c2fba68..578c2aa 100644
--- a/README.md
+++ b/README.md
@@ -31,9 +31,10 @@
-**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
@@ -45,7 +46,7 @@ npm install typescript-monads
-
+
```
@@ -54,7 +55,7 @@ var someRemoteValue;
typescriptMonads.maybe(someRemoteValue).tapSome(console.log)
```
-# Usage
+# Example Usage
* [Maybe](#maybe)
* [Either](#either)
@@ -92,7 +93,6 @@ maybe(process.env.DB_URL)
})
```
-
# Either
TODO
diff --git a/src/interfaces/maybe.interface.ts b/src/interfaces/maybe.interface.ts
index 5d0c034..fb726f5 100644
--- a/src/interfaces/maybe.interface.ts
+++ b/src/interfaces/maybe.interface.ts
@@ -1,4 +1,4 @@
-import { IMonad } from "./monad.interface"
+import { IMonad } from './monad.interface'
/**
* Define a contract to unwrap Maybe object
diff --git a/src/interfaces/result.interface.ts b/src/interfaces/result.interface.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/monads/either.ts b/src/monads/either.ts
index 955f223..a880c1e 100644
--- a/src/monads/either.ts
+++ b/src/monads/either.ts
@@ -1,4 +1,4 @@
-import { IEither, IEitherPattern } from "../interfaces"
+import { IEither, IEitherPattern } from '../interfaces'
const exists = (t: T) => t !== null && t !== undefined
const bothExist = (left?: L) => (right?: R) => exists(left) && exists(right)
diff --git a/src/monads/index.ts b/src/monads/index.ts
index 975cd4a..9f55089 100644
--- a/src/monads/index.ts
+++ b/src/monads/index.ts
@@ -1,4 +1,5 @@
-export { monad } from './monad'
-export { maybe } from './maybe'
-export { either } from './either'
-export { reader } from './reader'
\ No newline at end of file
+export * from './monad'
+export * from './maybe'
+export * from './either'
+export * from './reader'
+export * from './result'
\ No newline at end of file
diff --git a/src/monads/maybe.ts b/src/monads/maybe.ts
index afa58f9..16741eb 100644
--- a/src/monads/maybe.ts
+++ b/src/monads/maybe.ts
@@ -1,4 +1,4 @@
-import { IMaybe, IMaybePattern } from "../interfaces"
+import { IMaybe, IMaybePattern } from '../interfaces'
const isEmpty = (value: T) => value === null || value === undefined
const isNotEmpty = (value: T) => !isEmpty(value)
diff --git a/src/monads/monad.ts b/src/monads/monad.ts
index 38829cc..0515304 100644
--- a/src/monads/monad.ts
+++ b/src/monads/monad.ts
@@ -1,4 +1,4 @@
-import { mapping, IMonad } from "../interfaces"
+import { mapping, IMonad } from '../interfaces'
// tslint:disable:readonly-array
export const monad = (x: T, ...args: any[]): IMonad => {
diff --git a/src/monads/reader.ts b/src/monads/reader.ts
index 3f64374..f0cfea7 100644
--- a/src/monads/reader.ts
+++ b/src/monads/reader.ts
@@ -1,4 +1,4 @@
-import { IReader } from "../interfaces"
+import { IReader } from '../interfaces'
// tslint:disable:no-this
export const reader = (fn: (config: E) => A): IReader => {
diff --git a/src/monads/result.ts b/src/monads/result.ts
new file mode 100644
index 0000000..ea70ee3
--- /dev/null
+++ b/src/monads/result.ts
@@ -0,0 +1,97 @@
+import { IMaybe } from '../interfaces'
+import { maybe } from './maybe'
+
+const returnTrue = () => true
+const returnFalse = () => false
+const returnValue = (val: T) => () => val
+const returnMaybe = (val: T) => () => maybe(val)
+const throwReferenceError = (message: string) => () => { throw new ReferenceError(message) }
+
+type Predicate = () => boolean
+
+export interface IResultMatchPattern {
+ readonly ok: (val: T) => U
+ readonly fail: (val: E) => U
+}
+
+export interface IResult {
+ isOk(): boolean
+ isFail(): boolean
+ maybeOk(): IMaybe
+ maybeFail(): IMaybe
+ unwrap(): T | never
+ unwrapOr(opt: T): T
+ unwrapFail(): E | never
+ match(fn: IResultMatchPattern): M
+ map(fn: (val: T) => M): IResult
+ mapFail(fn: (err: E) => M): IResult
+ flatMap(fn: (val: T) => IResult): IResult
+}
+
+export interface IResultOk extends IResult {
+ unwrap(): T
+ unwrapOr(opt: T): T
+ unwrapFail(): never
+ match(fn: IResultMatchPattern): M
+ map(fn: (val: T) => M): IResultOk
+ mapFail(fn: (err: E) => M): IResultOk
+}
+
+export interface IResultFail extends IResult {
+ unwrap(): never
+ unwrapOr(opt: T): T
+ unwrapFail(): E
+ match(fn: IResultMatchPattern): M
+ map(fn: (val: T) => M): IResultFail
+ mapFail(fn: (err: E) => M): IResultFail
+ flatMap(fn: (val: T) => IResult): IResultFail
+}
+
+export const ok = (val: T): IResultOk => {
+ return {
+ isOk: returnTrue,
+ isFail: returnFalse,
+ maybeOk: returnMaybe(val),
+ maybeFail: maybe,
+ unwrap: returnValue(val),
+ unwrapOr: _ => val,
+ unwrapFail: throwReferenceError('Cannot unwrap a success'),
+ map: (fn: (val: T) => M) => ok(fn(val)),
+ mapFail: (_: (err: E) => M) => ok(val),
+ flatMap: (fn: (val: T) => IResult) => fn(val),
+ match: (fn: IResultMatchPattern) => fn.ok(val)
+ }
+}
+
+export const fail = (err: E): IResultFail => {
+ return {
+ isOk: returnFalse,
+ isFail: returnTrue,
+ maybeOk: maybe,
+ maybeFail: returnMaybe(err),
+ unwrap: throwReferenceError('Cannot unwrap a failure'),
+ unwrapOr: opt => opt,
+ unwrapFail: returnValue(err),
+ map: (_: (val: T) => M) => fail(err),
+ mapFail: (fn: (err: E) => M) => fail(fn(err)),
+ flatMap: (_: (val: T) => IResult) => fail(err),
+ match: (fn: IResultMatchPattern) => fn.fail(err)
+ }
+}
+
+/**
+ * Utility function to quickly create ok/fail pairs.
+ */
+export const result = (predicate: Predicate, okValue: T, failValue: E): IResult =>
+ predicate()
+ ? ok(okValue)
+ : fail(failValue)
+
+/**
+* Utility function to quickly create ok/fail pairs, curried variant.
+*/
+export const curriedResult =
+ (predicate: Predicate) =>
+ (okValue: T) =>
+ (failValue: E): IResult =>
+ result(predicate, okValue, failValue)
diff --git a/src/util/maybe-env.ts b/src/util/maybe-env.ts
index 5f634f7..a1c22e2 100644
--- a/src/util/maybe-env.ts
+++ b/src/util/maybe-env.ts
@@ -1,4 +1,4 @@
-import { reader, maybe } from ".."
+import { reader, maybe } from '..'
export interface GetFromEnvironmentReader {
readEnv(key: string): string | undefined
diff --git a/test/interfaces/index.spec.ts b/test/interfaces/index.spec.ts
index 2a9c05e..2e26f43 100644
--- a/test/interfaces/index.spec.ts
+++ b/test/interfaces/index.spec.ts
@@ -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
})
})
\ No newline at end of file
diff --git a/test/monads/result.spec.ts b/test/monads/result.spec.ts
new file mode 100644
index 0000000..199e663
--- /dev/null
+++ b/test/monads/result.spec.ts
@@ -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)
+ })
+ })
+})
diff --git a/tslint.json b/tslint.json
index ecf3f72..18a8d24 100644
--- a/tslint.json
+++ b/tslint.json
@@ -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"]
}
}
\ No newline at end of file