From 439fb9ee85ec407359be9bbeceb6952391844943 Mon Sep 17 00:00:00 2001 From: Kristof Hermans Date: Wed, 20 Dec 2017 15:57:36 +0100 Subject: [PATCH] Either implementation --- .eslintrc | 3 +- README.md | 1 + dist/cjs/subterfuge.js | 2 +- dist/es/subterfuge.js | 2 +- package.json | 25 +---- src/containers/box.spec.js | 4 +- src/containers/either.js | 13 +++ src/containers/either.spec.js | 163 +++++++++++++++++++++++++++++++++ src/containers/lazybox.spec.js | 2 +- 9 files changed, 189 insertions(+), 26 deletions(-) create mode 100644 src/containers/either.js create mode 100644 src/containers/either.spec.js diff --git a/.eslintrc b/.eslintrc index 22469b6..e971c17 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ "linebreak-style": [ "error", "unix" - ] + ], + "no-unused-vars": ["error", { "args": "none" }] } } \ No newline at end of file diff --git a/README.md b/README.md index a5e2f6a..cba9972 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ as a guide, making sure your own implementation behaves as expected. - [Box](#box) - [LazyBox](#lazybox) +- Either - composeLeft - composeRight - pipeLeft diff --git a/dist/cjs/subterfuge.js b/dist/cjs/subterfuge.js index f18832d..6ed9c36 100644 --- a/dist/cjs/subterfuge.js +++ b/dist/cjs/subterfuge.js @@ -1,4 +1,4 @@ -/* Subterfuge v0.5.1 +/* Subterfuge v0.6.0 * https://github.com/phixid/subterfuge * (c) 2017-2017 Kristof Hermans <@phixid> * Subterfuge may be freely distributed under the MIT license. diff --git a/dist/es/subterfuge.js b/dist/es/subterfuge.js index a2b95c7..5f267dc 100644 --- a/dist/es/subterfuge.js +++ b/dist/es/subterfuge.js @@ -1,4 +1,4 @@ -/* Subterfuge v0.5.1 +/* Subterfuge v0.6.0 * https://github.com/phixid/subterfuge * (c) 2017-2017 Kristof Hermans <@phixid> * Subterfuge may be freely distributed under the MIT license. diff --git a/package.json b/package.json index 116be97..0cc138e 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,11 @@ { "name": "subterfuge", - "version": "0.5.1", + "version": "0.6.0", "description": "Simple functional Javascript", - "keywords": [ - "functional", - "javascript" - ], + "keywords": ["functional", "javascript"], "author": "Kristof Hermans <@phixid>", "repository": "git@github.com:phixid/subterfuge.git", - "files": [ - "dist" - ], + "files": ["dist"], "main": "dist/cjs/subterfuge.js", "module": "dist/es/subterfuge.js", "scripts": { @@ -50,19 +45,9 @@ "rollup-plugin-commonjs": "^8.2.6", "rollup-plugin-node-resolve": "^3.0.0" }, - "jest": { - "bail": true, - "testMatch": [ - "**/src/**/*.(spec|test).js", - "**/**/__tests__/**/*.(spec|test).js" - ] - }, + "jest": { "bail": true, "testMatch": ["**/src/**/*.(spec|test).js", "**/**/__tests__/**/*.(spec|test).js"] }, "lint-staged": { - "*.js": [ - "eslint", - "prettier --write --single-quote --bracket-space=true --print-width=100", - "git add" - ] + "*.js": ["eslint", "prettier --write --single-quote --bracket-space=true --print-width=100", "git add"] }, "license": "MIT" } diff --git a/src/containers/box.spec.js b/src/containers/box.spec.js index e0daf4a..db521c5 100644 --- a/src/containers/box.spec.js +++ b/src/containers/box.spec.js @@ -2,9 +2,9 @@ import { Box } from './box'; import { isFunction, resemblesBox } from '../../__tests__/testUtilities'; import { addOne, double, randomNumberBetween1And10 } from '../../__tests__/utilities'; -const randomNumber = randomNumberBetween1And10; +const randomNumber = randomNumberBetween1And10(); -describe('A Box data type', () => { +describe('A Box container type', () => { it('is a function', () => { isFunction(Box); }); diff --git a/src/containers/either.js b/src/containers/either.js new file mode 100644 index 0000000..d8863fb --- /dev/null +++ b/src/containers/either.js @@ -0,0 +1,13 @@ +export const Left = value => ({ + map: func => Left(value), + fold: (errorhandler, successhandler) => errorhandler(value), + inspect: () => `Left(${value})` +}); + +export const Right = value => ({ + map: func => Right(func(value)), + fold: (errorhandler, successhandler) => successhandler(value), + inspect: () => `Right(${value})` +}); + +export const Either = value => (value == null ? Left(value) : Right(value)); diff --git a/src/containers/either.spec.js b/src/containers/either.spec.js new file mode 100644 index 0000000..c6995ec --- /dev/null +++ b/src/containers/either.spec.js @@ -0,0 +1,163 @@ +import { Either, Left, Right } from './either'; +import { isFunction, resemblesBox } from '../../__tests__/testUtilities'; +import { addOne, randomNumberBetween1And10 } from '../../__tests__/utilities'; + +describe('Either container type: code branching', () => { + let randomNumber = randomNumberBetween1And10(); + + it('is a function', () => { + isFunction(Either); + }); + + describe('input:', () => { + it('takes one parameter', () => { + expect(Either.length).toEqual(1); + }); + }); + + describe('output:', () => { + describe('Left container type:', () => { + const left = Left(randomNumber); + + it('is a function', () => { + isFunction(Left); + }); + + it('API looks like that of a Box', () => { + resemblesBox(Left()); + }); + + describe('map method:', () => { + it('returns a new Left', () => { + resemblesBox(Left(4).map(x => x)); + }); + + it('takes one parameter', () => { + expect(left.map.length).toEqual(1); + }); + + it('does not apply the functor to the value', () => { + let mockFn = jest.fn(); + Left().map(mockFn); + expect(mockFn).toHaveBeenCalledTimes(0); + }); + }); + + describe('fold method:', () => { + it('is a function', () => { + isFunction(Right().fold); + }); + + it('takes two parameters', () => { + expect(left.fold.length).toEqual(2); + }); + + it('applies the second functor to the value', () => { + let mock1 = jest.fn(); + let mock2 = jest.fn(); + + left.fold(mock1, mock2); + + expect(mock1).toHaveBeenCalledTimes(1); + expect(mock2).toHaveBeenCalledTimes(0); + expect(left.fold(() => 'error', addOne)).toEqual('error'); + }); + }); + + describe('inspect', () => { + it('is a function', () => { + isFunction(left.inspect); + }); + + it('takes no arguments', () => { + expect(left.inspect.length).toEqual(0); + }); + + it('returns the current value in a `Left(${})`-template', () => { + expect(left.inspect()).toEqual(`Left(${randomNumber})`); + }); + }); + }); + + describe('Right container type:', () => { + const right = Right(randomNumber); + + it('is a function', () => { + isFunction(Right); + }); + + it('API looks like that of a Box', () => { + resemblesBox(Right()); + }); + + describe('map method:', () => { + it('returns a new Right', () => { + resemblesBox(Right(4).map(x => x)); + }); + + it('takes one parameter', () => { + expect(right.map.length).toEqual(1); + }); + + it('applies the functor to the value', () => { + let mockFn = jest.fn(); + + right.map(mockFn); + expect(mockFn).toHaveBeenCalledTimes(1); + expect(mockFn).toBeCalledWith(randomNumber); + }); + }); + + describe('fold method:', () => { + it('is a function', () => { + isFunction(Right().fold); + }); + + it('takes two parameters', () => { + expect(right.fold.length).toEqual(2); + }); + + it('applies the second functor to the value', () => { + let mock1 = jest.fn(); + let mock2 = jest.fn(); + + right.fold(mock1, mock2); + + expect(mock1).toHaveBeenCalledTimes(0); + expect(mock2).toHaveBeenCalledTimes(1); + expect(right.fold(x => x, addOne)).toEqual(randomNumber + 1); + }); + }); + + describe('inspect', () => { + it('is a function', () => { + isFunction(right.inspect); + }); + + it('takes no arguments', () => { + expect(right.inspect.length).toEqual(0); + }); + + it('returns the current value in a `Right(${})`-template', () => { + expect(right.inspect()).toEqual(`Right(${randomNumber})`); + }); + }); + }); + + it('branches to a Left when parameter is null || undefined', () => { + expect( + Either(null) + .map(x => x + 1) + .fold(() => 'error', x => x) + ).toEqual('error'); + }); + + it('branches to a Right when parameter is not null || undefined', () => { + expect( + Either(randomNumber) + .map(x => x + 1) + .fold(() => 'error', x => x / 2) + ).toEqual((randomNumber + 1) / 2); + }); + }); +}); diff --git a/src/containers/lazybox.spec.js b/src/containers/lazybox.spec.js index e9d7b3e..1593b43 100644 --- a/src/containers/lazybox.spec.js +++ b/src/containers/lazybox.spec.js @@ -6,7 +6,7 @@ const mockFn = jest.fn(); const randomNumber = randomNumberBetween1And10; const giveRandomNumber = () => randomNumber; -describe('A LazyBox data type', () => { +describe('A LazyBox container type', () => { it('is a function', () => { isFunction(LazyBox); });