From d894264e10b051f4caa1c506ee3f16fe2a852132 Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 16:54:28 -0600 Subject: [PATCH 1/8] feat: introduce result monad --- src/monads/index.ts | 9 +++--- src/monads/result.ts | 54 +++++++++++++++++++++++++++++++++++ test/interfaces/index.spec.ts | 5 ++-- test/monads/result.spec.ts | 45 +++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/monads/result.ts create mode 100644 test/monads/result.spec.ts 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/result.ts b/src/monads/result.ts new file mode 100644 index 0000000..6f232e1 --- /dev/null +++ b/src/monads/result.ts @@ -0,0 +1,54 @@ +import { IMaybe } from "../interfaces" +import { maybe } from "./maybe" + +const returnTrue = () => true +const returnFalse = () => false + +export interface IResult { + isOk(): boolean + isFail(): boolean + maybeOk(): IMaybe + maybeFail(): IMaybe + unwrap(): T | never + unwrapOr(opt: T): T + unwrapFail(): E | never + // map(fn: (val: T) => M): IResult + // match(fn: Match): U + // map_err(fn: (err: E) => U): Result + // and_then(fn: (val: T) => Result): Result +} + +export interface IResultOk extends IResult { + unwrap(): T + unwrapOr(opt: T): T + unwrapFail(): never +} +export interface IResultFail extends IResult { + unwrap(): never + unwrapOr(opt: T): T + unwrapFail(): E +} + +export const ok = (val: T): IResultOk => { + return { + isOk: returnTrue, + isFail: returnFalse, + maybeOk: () => maybe(val), + maybeFail: maybe, + unwrap: () => val, + unwrapOr: _ => val, + unwrapFail: () => { throw new Error() } + } +} + +export const fail = (err: E): IResultFail => { + return { + isOk: returnTrue, + isFail: returnFalse, + maybeOk: maybe, + maybeFail: () => maybe(err), + unwrap: () => { throw new Error('Cannot unwrap a failure') }, + unwrapOr: opt => opt, + unwrapFail: () => err + } +} 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..ca79ec7 --- /dev/null +++ b/test/monads/result.spec.ts @@ -0,0 +1,45 @@ +import { ok } 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) + }) + }) +}) From 337ff103b75583fc1af13bc5e097209ced11499a Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 20:15:59 -0600 Subject: [PATCH 2/8] tests --- src/monads/result.ts | 19 +++++++++------- test/monads/result.spec.ts | 44 +++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/monads/result.ts b/src/monads/result.ts index 6f232e1..1ec5688 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -3,6 +3,9 @@ import { maybe } from "./maybe" const returnTrue = () => true const returnFalse = () => false +const returnValue = (val: T) => () => val +const returnMaybe = (val: T) => () => maybe(val) +const throwError = (message: string) => () => { throw new Error(message) } export interface IResult { isOk(): boolean @@ -33,22 +36,22 @@ export const ok = (val: T): IResultOk => { return { isOk: returnTrue, isFail: returnFalse, - maybeOk: () => maybe(val), + maybeOk: returnMaybe(val), maybeFail: maybe, - unwrap: () => val, + unwrap: returnValue(val), unwrapOr: _ => val, - unwrapFail: () => { throw new Error() } + unwrapFail: throwError('Cannot unwrap a success') } } export const fail = (err: E): IResultFail => { return { - isOk: returnTrue, - isFail: returnFalse, + isOk: returnFalse, + isFail: returnTrue, maybeOk: maybe, - maybeFail: () => maybe(err), - unwrap: () => { throw new Error('Cannot unwrap a failure') }, + maybeFail: returnMaybe(err), + unwrap: throwError('Cannot unwrap a failure'), unwrapOr: opt => opt, - unwrapFail: () => err + unwrapFail: returnValue(err) } } diff --git a/test/monads/result.spec.ts b/test/monads/result.spec.ts index ca79ec7..438bc71 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -1,4 +1,4 @@ -import { ok } from '../../src/monads' +import { ok, fail } from '../../src/monads' describe('result', () => { describe('ok', () => { @@ -30,7 +30,7 @@ describe('result', () => { const _sut = ok('Test') .maybeOk() .valueOr('Some Other') - + expect(_sut).toEqual('Test') }) @@ -38,8 +38,46 @@ describe('result', () => { const _sut = ok('Test') .maybeFail() .valueOrUndefined() - + expect(_sut).toEqual(undefined) }) }) + + 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') + }) + }) }) From 3e410cd0720e7a8ac7c37fab6defba1d914f4be7 Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 20:17:16 -0600 Subject: [PATCH 3/8] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2fba68..bd8a39b 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ npm install typescript-monads - + ``` From 51016ef10bde29b5a0caaf9aaed9c58cd6e44c84 Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 20:59:15 -0600 Subject: [PATCH 4/8] wip --- README.md | 3 +-- src/monads/result.ts | 26 +++++++++++++++++++++++--- test/monads/result.spec.ts | 14 +++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd8a39b..ff45a20 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@

-**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 @@ -92,7 +92,6 @@ maybe(process.env.DB_URL) }) ``` - # Either TODO diff --git a/src/monads/result.ts b/src/monads/result.ts index 1ec5688..1518084 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -5,7 +5,8 @@ const returnTrue = () => true const returnFalse = () => false const returnValue = (val: T) => () => val const returnMaybe = (val: T) => () => maybe(val) -const throwError = (message: string) => () => { throw new Error(message) } +const throwReferenceError = (message: string) => () => { throw new ReferenceError(message) } +type Predicate = () => boolean export interface IResult { isOk(): boolean @@ -40,7 +41,7 @@ export const ok = (val: T): IResultOk => { maybeFail: maybe, unwrap: returnValue(val), unwrapOr: _ => val, - unwrapFail: throwError('Cannot unwrap a success') + unwrapFail: throwReferenceError('Cannot unwrap a success') } } @@ -50,8 +51,27 @@ export const fail = (err: E): IResultFail => { isFail: returnTrue, maybeOk: maybe, maybeFail: returnMaybe(err), - unwrap: throwError('Cannot unwrap a failure'), + unwrap: throwReferenceError('Cannot unwrap a failure'), unwrapOr: opt => opt, unwrapFail: returnValue(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 in curried form. +// */ +// export const curriedResult = +// (predicate: Predicate) => +// (okValue: T) => +// (failValue: E): IResult => +// predicate() +// ? ok(okValue) +// : fail(failValue) \ No newline at end of file diff --git a/test/monads/result.spec.ts b/test/monads/result.spec.ts index 438bc71..344d4df 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -1,4 +1,4 @@ -import { ok, fail } from '../../src/monads' +import { ok, fail, result } from '../../src/monads' describe('result', () => { describe('ok', () => { @@ -80,4 +80,16 @@ describe('result', () => { expect(fail('123').unwrapOr('456')).toEqual('456') }) }) + + 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) + }) + }) }) From e97c8282d0ea721ff3b2e8b1b2aa39bca1a70559 Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 22:10:14 -0600 Subject: [PATCH 5/8] lint --- src/interfaces/maybe.interface.ts | 2 +- src/monads/either.ts | 2 +- src/monads/maybe.ts | 2 +- src/monads/monad.ts | 2 +- src/monads/reader.ts | 2 +- src/monads/result.ts | 36 +++++++++++++++---------------- src/util/maybe-env.ts | 2 +- test/monads/result.spec.ts | 32 +++++++++++++++++++++++++-- tslint.json | 3 ++- 9 files changed, 55 insertions(+), 28 deletions(-) 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/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/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 index 1518084..d8aa8e3 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -1,11 +1,12 @@ -import { IMaybe } from "../interfaces" -import { maybe } from "./maybe" +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 IResult { @@ -16,21 +17,25 @@ export interface IResult { unwrap(): T | never unwrapOr(opt: T): T unwrapFail(): E | never - // map(fn: (val: T) => M): IResult + map(fn: (val: T) => M): IResult + mapFail(fn: (err: E) => M): IResult // match(fn: Match): U - // map_err(fn: (err: E) => U): Result // and_then(fn: (val: T) => Result): Result } export interface IResultOk extends IResult { unwrap(): T unwrapOr(opt: T): T - unwrapFail(): never + unwrapFail(): never, + 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 + unwrapFail(): E, + map(fn: (val: T) => M): IResultFail + mapFail(fn: (err: E) => M): IResultFail } export const ok = (val: T): IResultOk => { @@ -41,7 +46,9 @@ export const ok = (val: T): IResultOk => { maybeFail: maybe, unwrap: returnValue(val), unwrapOr: _ => val, - unwrapFail: throwReferenceError('Cannot unwrap a success') + unwrapFail: throwReferenceError('Cannot unwrap a success'), + map: (fn: (val: T) => M) => ok(fn(val)), + mapFail: (_: (err: E) => M) => ok(val) } } @@ -53,7 +60,9 @@ export const fail = (err: E): IResultFail => { maybeFail: returnMaybe(err), unwrap: throwReferenceError('Cannot unwrap a failure'), unwrapOr: opt => opt, - unwrapFail: returnValue(err) + unwrapFail: returnValue(err), + map: (_: (val: T) => M) => fail(err), + mapFail: (fn: (err: E) => M) => fail(fn(err)) } } @@ -64,14 +73,3 @@ export const result = (predicate: Predicate, okValue: T, failValue: E): IR predicate() ? ok(okValue) : fail(failValue) - -// /** -// * Utility function to quickly create ok/fail pairs in curried form. -// */ -// export const curriedResult = -// (predicate: Predicate) => -// (okValue: T) => -// (failValue: E): IResult => -// predicate() -// ? ok(okValue) -// : fail(failValue) \ No newline at end of file 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/monads/result.spec.ts b/test/monads/result.spec.ts index 344d4df..3159b4f 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -12,12 +12,12 @@ describe('result', () => { it('should unwrap', () => { expect(ok(1).unwrap()).toEqual(1) - expect(ok("Test").unwrap()).toEqual("Test") + 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") + expect(ok('Test').unwrapOr('Some Other')).toEqual('Test') }) it('should throw an exception whe "unwrapOrFail" called on an ok value', () => { @@ -41,6 +41,20 @@ describe('result', () => { 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) + }) }) describe('fail', () => { @@ -79,6 +93,20 @@ describe('result', () => { 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') + }) }) describe('result', () => { 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 From c0aa9f6eb98fab21301d326cb30ecbc6cab073ef Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 22:24:43 -0600 Subject: [PATCH 6/8] flatMap --- README.md | 5 +++-- src/monads/result.ts | 10 +++++++--- test/monads/result.spec.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ff45a20..578c2aa 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ **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 @@ -54,7 +55,7 @@ var someRemoteValue; typescriptMonads.maybe(someRemoteValue).tapSome(console.log) ``` -# Usage +# Example Usage * [Maybe](#maybe) * [Either](#either) diff --git a/src/monads/result.ts b/src/monads/result.ts index d8aa8e3..e8ec296 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -19,8 +19,8 @@ export interface IResult { unwrapFail(): E | never map(fn: (val: T) => M): IResult mapFail(fn: (err: E) => M): IResult + flatMap(fn: (val: T) => IResult): IResult // match(fn: Match): U - // and_then(fn: (val: T) => Result): Result } export interface IResultOk extends IResult { @@ -30,12 +30,14 @@ export interface IResultOk extends IResult { 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, map(fn: (val: T) => M): IResultFail mapFail(fn: (err: E) => M): IResultFail + flatMap(fn: (val: T) => IResult): IResultFail } export const ok = (val: T): IResultOk => { @@ -48,7 +50,8 @@ export const ok = (val: T): IResultOk => { unwrapOr: _ => val, unwrapFail: throwReferenceError('Cannot unwrap a success'), map: (fn: (val: T) => M) => ok(fn(val)), - mapFail: (_: (err: E) => M) => ok(val) + mapFail: (_: (err: E) => M) => ok(val), + flatMap: (fn: (val: T) => IResult) => fn(val) } } @@ -62,7 +65,8 @@ export const fail = (err: E): IResultFail => { unwrapOr: opt => opt, unwrapFail: returnValue(err), map: (_: (val: T) => M) => fail(err), - mapFail: (fn: (err: E) => M) => fail(fn(err)) + mapFail: (fn: (err: E) => M) => fail(fn(err)), + flatMap: (_: (val: T) => IResult) => fail(err) } } diff --git a/test/monads/result.spec.ts b/test/monads/result.spec.ts index 3159b4f..cd899f7 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -55,6 +55,14 @@ describe('result', () => { .unwrap() expect(sut).toEqual(1) }) + + it('should flatMap', () => { + const sut = ok(1) + .flatMap(a => ok(a.toString())) + .unwrap() + + expect(sut).toEqual('1') + }) }) describe('fail', () => { @@ -107,6 +115,14 @@ describe('result', () => { .unwrapFail() expect(sut).toEqual('1') }) + + it('should not flatMap', () => { + const sut = fail(1) + .flatMap(a => ok(a.toString())) + .unwrapFail() + + expect(sut).toEqual(1) + }) }) describe('result', () => { From aaab5b46f967552e844745648d1fc5b975763afb Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 22:56:23 -0600 Subject: [PATCH 7/8] match --- src/interfaces/result.interface.ts | 0 src/monads/result.ts | 19 ++++++++++++++----- test/monads/result.spec.ts | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 src/interfaces/result.interface.ts 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/result.ts b/src/monads/result.ts index e8ec296..a88d636 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -9,6 +9,11 @@ const throwReferenceError = (message: string) => () => { throw new ReferenceErro type Predicate = () => boolean +export interface IResultMatchPattern { + readonly ok: (val: T) => U + readonly fail: (val: E) => U +} + export interface IResult { isOk(): boolean isFail(): boolean @@ -17,16 +22,17 @@ export interface IResult { 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 - // match(fn: Match): U } export interface IResultOk extends IResult { unwrap(): T unwrapOr(opt: T): T - unwrapFail(): never, + unwrapFail(): never + match(fn: IResultMatchPattern): M map(fn: (val: T) => M): IResultOk mapFail(fn: (err: E) => M): IResultOk } @@ -34,7 +40,8 @@ export interface IResultOk extends IResult { export interface IResultFail extends IResult { unwrap(): never unwrapOr(opt: T): T - unwrapFail(): E, + unwrapFail(): E + match(fn: IResultMatchPattern): M map(fn: (val: T) => M): IResultFail mapFail(fn: (err: E) => M): IResultFail flatMap(fn: (val: T) => IResult): IResultFail @@ -51,7 +58,8 @@ export const ok = (val: T): IResultOk => { 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) + flatMap: (fn: (val: T) => IResult) => fn(val), + match: (fn: IResultMatchPattern) => fn.ok(val) } } @@ -66,7 +74,8 @@ export const fail = (err: E): IResultFail => { unwrapFail: returnValue(err), map: (_: (val: T) => M) => fail(err), mapFail: (fn: (err: E) => M) => fail(fn(err)), - flatMap: (_: (val: T) => IResult) => fail(err) + flatMap: (_: (val: T) => IResult) => fail(err), + match: (fn: IResultMatchPattern) => fn.fail(err) } } diff --git a/test/monads/result.spec.ts b/test/monads/result.spec.ts index cd899f7..63ca862 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -63,6 +63,16 @@ describe('result', () => { expect(sut).toEqual('1') }) + + it('should match', () => { + const sut = ok(1) + .match({ + fail: _ => 2, + ok: val => val + }) + + expect(sut).toEqual(1) + }) }) describe('fail', () => { @@ -123,6 +133,16 @@ describe('result', () => { expect(sut).toEqual(1) }) + + it('should match', () => { + const sut = fail(1) + .match({ + fail: _ => 2, + ok: val => val + }) + + expect(sut).toEqual(2) + }) }) describe('result', () => { From c0d036df3859e1977b36115f5e3a38f6d5c6b4ae Mon Sep 17 00:00:00 2001 From: Patrick Michalina Date: Wed, 9 Jan 2019 22:59:23 -0600 Subject: [PATCH 8/8] curried --- src/monads/result.ts | 9 +++++++++ test/monads/result.spec.ts | 7 ++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/monads/result.ts b/src/monads/result.ts index a88d636..ea70ee3 100644 --- a/src/monads/result.ts +++ b/src/monads/result.ts @@ -86,3 +86,12 @@ export const result = (predicate: Predicate, okValue: T, failValue: E): IR 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/test/monads/result.spec.ts b/test/monads/result.spec.ts index 63ca862..199e663 100644 --- a/test/monads/result.spec.ts +++ b/test/monads/result.spec.ts @@ -1,4 +1,4 @@ -import { ok, fail, result } from '../../src/monads' +import { ok, fail, result, curriedResult } from '../../src/monads' describe('result', () => { describe('ok', () => { @@ -155,5 +155,10 @@ describe('result', () => { 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) + }) }) })