diff --git a/src/Err.ts b/src/Err.ts index 9f130ba..0b0fa4c 100644 --- a/src/Err.ts +++ b/src/Err.ts @@ -40,7 +40,7 @@ class Err extends Result { public assign( k: K, - other: Result | ((a: A) => Result), + other: Result | ((a: A) => Result) ): Result { return new Err(this.error); } @@ -48,6 +48,11 @@ class Err extends Result { public do(fn: (a: A) => void): Result { return new Err(this.error); } + + public elseDo(fn: (err: E) => void): Result { + fn(this.error); + return this; + } } /** diff --git a/src/Ok.ts b/src/Ok.ts index 1cdc512..d380b4c 100644 --- a/src/Ok.ts +++ b/src/Ok.ts @@ -34,17 +34,9 @@ class Ok extends Result { return matcher.Ok(this.value); } - public ap(result: Result): Result { - if (typeof this.value !== 'function') { - throw new TypeError(`'ap' can only be applied to functions: ${JSON.stringify(this.value)}`); - } - - return result.map(this.value); - } - public assign( k: K, - other: Result | ((a: A) => Result), + other: Result | ((a: A) => Result) ): Result { const result = other instanceof Result ? other : other(this.value); return result.map(b => ({ @@ -57,6 +49,10 @@ class Ok extends Result { fn(this.value); return new Ok(this.value); } + + public elseDo(fn: (err: E) => void): Result { + return this; + } } /** diff --git a/src/Result.ts b/src/Result.ts index 71ad41c..822a5e5 100644 --- a/src/Result.ts +++ b/src/Result.ts @@ -48,13 +48,6 @@ abstract class Result { */ public abstract cata(matcher: Catamorphism): B; - /** - * Apply the value of a successful result to a function result. If this - * result is an Err, nothing happens. If this result is NOT a function, - * a TypeError is raised. - */ - public abstract ap(result: Result): Result; - /** * Encapsulates a common pattern of needing to build up an Object from * a series of Result values. This is often solved by nesting `andThen` calls @@ -69,7 +62,7 @@ abstract class Result { */ public abstract assign( k: K, - other: Result | ((a: A) => Result), + other: Result | ((a: A) => Result) ): Result; /** @@ -90,6 +83,22 @@ abstract class Result { * */ public abstract do(fn: (a: A) => void): Result; + + /** + * Inject a side-effectual operation into a chain of Result computations. + * + * The side effect only runs when there is an error (Err). + * + * The value will remain unchanged during the `elseDo` operation. + * + * ok({}) + * .assign('foo', ok(42)) + * .assign('bar', ok('hello')) + * .do(scope => console.log('Scope: ', JSON.stringify(scope))) + * .map(doSomethingElse) + * + */ + public abstract elseDo(fn: (err: E) => void): Result; } export default Result; diff --git a/tests/Err.test.ts b/tests/Err.test.ts index e6703d0..39986a4 100644 --- a/tests/Err.test.ts +++ b/tests/Err.test.ts @@ -3,7 +3,10 @@ import { err, ok } from './../src/index'; test('Err.getOrElse', t => { const result = err('foo'); - t.equal('bar', result.getOrElse(() => 'bar')); + t.equal( + 'bar', + result.getOrElse(() => 'bar') + ); t.end(); }); @@ -35,37 +38,44 @@ test('Err.cata', t => { }); test('Err.mapError', t => { - err('foo').mapError(m => m.toUpperCase()).cata({ - Err: err => t.equal('FOO', err), - Ok: v => t.fail('should not have passed'), - }); + err('foo') + .mapError(m => m.toUpperCase()) + .cata({ + Err: err => t.equal('FOO', err), + Ok: v => t.fail('should not have passed'), + }); t.end(); }); -test('Err.ap', t => { - const fn = (a: string) => (b: number) => ({ a, b }); - - err('oops!').ap(ok('hi')).ap(ok(42)).cata({ - Err: m => t.pass(`Failed as expected: ${m}`), - Ok: v => t.fail(`Should have failed: ${JSON.stringify(v)}`), - }); - +test('Err.assign', t => { + ok({}) + .assign('x', ok(42)) + .assign('y', () => err('ooops!')) + .cata({ + Err: m => t.pass(`Failed as expected: ${m}`), + Ok: v => t.fail(`Should have failed: ${JSON.stringify(v)}`), + }); t.end(); }); -test('Err.assign', t => { - ok({}).assign('x', ok(42)).assign('y', () => err('ooops!')).cata({ - Err: m => t.pass(`Failed as expected: ${m}`), - Ok: v => t.fail(`Should have failed: ${JSON.stringify(v)}`), - }); +test('Err.do', t => { + err('oops!') + .do(v => t.fail(`Should NOT run side effect: ${v}`)) + .cata({ + Err: m => t.pass(`Should be an error: ${m}`), + Ok: v => t.fail(`Should not succeeded: ${JSON.stringify(v)}`), + }); + t.end(); }); -test('Err.do', t => { - err('oops!').do(v => t.fail(`Should NOT run side effect: ${v}`)).cata({ - Err: m => t.pass(`Should be an error: ${m}`), - Ok: v => t.fail(`Should not succeeded: ${JSON.stringify(v)}`), - }); +test('Err.elseDo', t => { + err('oops!') + .elseDo(v => t.pass(`Error side effect ran: ${v}`)) + .cata({ + Err: m => t.pass(`Should be an error: ${m}`), + Ok: v => t.fail(`Should not succeed: ${JSON.stringify(v)}`), + }); t.end(); }); diff --git a/tests/Ok.test.ts b/tests/Ok.test.ts index 3eda344..93cbcf7 100644 --- a/tests/Ok.test.ts +++ b/tests/Ok.test.ts @@ -1,15 +1,21 @@ import * as test from 'tape'; -import { err, ok } from './../src/index'; +import { ok } from './../src/index'; test('Ok.getOrElse', t => { const result = ok('foo'); - t.equal('foo', result.getOrElse(() => 'bar')); + t.equal( + 'foo', + result.getOrElse(() => 'bar') + ); t.end(); }); test('OK.map', t => { const result = ok('foo'); - t.equal('FOO', result.map(s => s.toUpperCase()).getOrElse(() => '')); + t.equal( + 'FOO', + result.map(s => s.toUpperCase()).getOrElse(() => '') + ); t.end(); }); @@ -35,43 +41,46 @@ test('Ok.cata', t => { }); test('Ok.mapError', t => { - ok('foo').mapError(m => m.toUpperCase()).cata({ - Err: err => t.fail('should have passed'), - Ok: v => t.pass('Worked!'), - }); + ok('foo') + .mapError(m => m.toUpperCase()) + .cata({ + Err: err => t.fail('should have passed'), + Ok: v => t.pass('Worked!'), + }); t.end(); }); -test('Ok.ap', t => { - const fn = (a: string) => (b: number) => ({ a, b }); - - ok(fn).ap(ok('hi')).ap(ok(42)).cata({ - Err: m => t.fail(`Should have passed: ${m}`), - Ok: v => t.pass(`Worked!: ${JSON.stringify(v)}`), - }); - - ok(fn).ap(ok('hi')).ap(err('oops!')).cata({ - Err: m => t.pass(`ap failed: ${m}`), - Ok: v => t.fail(`should have failed: ${v}`), - }); - +test('Ok.assign', t => { + ok({}) + .assign('x', ok(42)) + .assign('y', v => ok(String(v.x + 8))) + .cata({ + Err: m => t.fail(`Should have succeeded: ${m}`), + Ok: v => t.deepEqual(v, { x: 42, y: '50' }), + }); t.end(); }); -test('Ok.assign', t => { - ok({}).assign('x', ok(42)).assign('y', v => ok(String(v.x + 8))).cata({ - Err: m => t.fail(`Should have succeeded: ${m}`), - Ok: v => t.deepEqual(v, { x: 42, y: '50' }), - }); +test('Ok.do', t => { + ok({}) + .assign('x', ok(42)) + .do(scope => t.pass(`'do' should run: ${JSON.stringify(scope)}`)) + .cata({ + Err: m => t.fail(`Should have succeeded: ${m}`), + Ok: v => t.deepEqual(v, { x: 42 }), + }); + t.end(); }); -test('Ok.do', t => { - ok({}).assign('x', ok(42)).do(scope => t.pass(`'do' should run: ${JSON.stringify(scope)}`)).cata({ - Err: m => t.fail(`Should have succeeded: ${m}`), - Ok: v => t.deepEqual(v, { x: 42 }), - }); +test('Ok.elseDo', t => { + ok({ x: 42 }) + .elseDo(err => t.fail(`Error side effect should not run: ${err}`)) + .cata({ + Err: m => t.fail(`Should have succeeded: ${m}`), + Ok: v => t.deepEqual(v, { x: 42 }), + }); t.end(); });