From f97663b1767794f6ea99c1d599d9ddb7a549200e Mon Sep 17 00:00:00 2001 From: David Chambers Date: Sat, 4 Mar 2017 23:31:45 +0100 Subject: [PATCH] everything --- .circleci/config.yml | 36 +++ .config | 2 + .eslintrc.json | 16 ++ .gitignore | 2 + .npmrc | 1 + CONTRIBUTING.md | 27 +++ LICENSE | 25 ++ index.js | 527 +++++++++++++++++++++++++++++++++++++++++++ package.json | 37 +++ test/.eslintrc.json | 6 + test/index.js | 411 +++++++++++++++++++++++++++++++++ test/mocha.opts | 1 + 12 files changed, 1091 insertions(+) create mode 100644 .circleci/config.yml create mode 100644 .config create mode 100644 .eslintrc.json create mode 100644 .npmrc create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 index.js create mode 100644 package.json create mode 100644 test/.eslintrc.json create mode 100644 test/index.js create mode 100644 test/mocha.opts diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..aeff0ee --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,36 @@ +version: 2 + +jobs: + build: + docker: + - image: circleci/node:6 + environment: + NPM_CONFIG_COLOR: false + NPM_CONFIG_LOGLEVEL: warn + NPM_CONFIG_PROGRESS: false + NVM_DIR: /home/circleci/.nvm + parallelism: 3 + steps: + - checkout + - restore_cache: + keys: + - nvm-cache-{{ checksum ".circleci/config.yml" }} + - run: curl https://raw.githubusercontent.com/creationix/nvm/v0.33.6/install.sh | bash + - save_cache: + key: nvm-cache-{{ checksum ".circleci/config.yml" }} + paths: + - /home/circleci/.nvm + - run: + name: npm install && npm test + command: | + test_with_version() { + source "$NVM_DIR/nvm.sh" + nvm install $1 + nvm exec $1 npm install + nvm exec $1 npm test + } + case $CIRCLE_NODE_INDEX in + 0) npm install && npm test ;; + 1) test_with_version 8 ;; + 2) test_with_version 10 ;; + esac diff --git a/.config b/.config new file mode 100644 index 0000000..366dbb9 --- /dev/null +++ b/.config @@ -0,0 +1,2 @@ +repo-owner = sanctuary-js +repo-name = sanctuary-either diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..fa09497 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "root": true, + "extends": ["./node_modules/sanctuary-style/eslint-es3.json"], + "overrides": [ + { + "files": ["*.md"], + "globals": {"$": false, "Either": false, "Left": false, "Right": false, "S": false, "Z": false, "show": false, "type": false} + }, + { + "files": ["index.js"], + "rules": { + "no-unused-vars": ["error", {"vars": "all", "varsIgnorePattern": "^S$", "args": "none"}] + } + } + ] +} diff --git a/.gitignore b/.gitignore index e69de29..cba87a3 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +/coverage/ +/node_modules/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dd7edee --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,27 @@ +# Contributing + +Note: __README.md__ is generated from comments in __index.js__. Do not modify +__README.md__ directly. + +1. Update local master branch: + + $ git checkout master + $ git pull upstream master + +2. Create feature branch: + + $ git checkout -b feature-x + +3. Make one or more atomic commits, and ensure that each commit has a + descriptive commit message. Commit messages should be line wrapped + at 72 characters. + +4. Run `npm test`, and address any errors. Preferably, fix commits in place + using `git rebase` or `git commit --amend` to make the changes easier to + review. + +5. Push: + + $ git push origin feature-x + +6. Open a pull request. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ffd105b --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +The MIT License (MIT) + +Copyright (c) 2018 Sanctuary +Copyright (c) 2016 Plaid Technologies, Inc. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/index.js b/index.js new file mode 100644 index 0000000..0918207 --- /dev/null +++ b/index.js @@ -0,0 +1,527 @@ +/* + _______ ___ _________ ___ ___ _______ ______ + / ____/\ / /\ /__ ___/\ / /\ / /\ / ____/\ / __ \ + / /\___\/ / / / \_/ /\__\/ / /_// / / / /\___\/ / /\/ /\ + / ____/\ / / / / / / / ___ / / / ____/\ / _/ / + / /\___\/ / / / / / / / /\_/ / / / /\___\/ / /| |\\/ + /______/\ /__/ / /__/ / /__/ //__/ / /______/\ /__/ |__| | + \______\/ \__\/ \__\/ \__\/ \__\/ \______\/ \__\/ \__\| + */ + +//. Fantasy Land +//. +//. # sanctuary-either +//. +//. The Either type represents values with two possibilities: a value of type +//. `Either a b` is either a Left whose value is of type `a` or a Right whose +//. value is of type `b`. + +(function(f) { + + 'use strict'; + + /* istanbul ignore else */ + if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = f (require ('sanctuary-show'), + require ('sanctuary-type-classes')); + } else if (typeof define === 'function' && define.amd != null) { + define (['sanctuary-show', 'sanctuary-type-classes'], f); + } else { + self.sanctuaryEither = f (self.sanctuaryShow, self.sanctuaryTypeClasses); + } + +} (function(show, Z) { + + 'use strict'; + + /* istanbul ignore if */ + if (typeof __doctest !== 'undefined') { + var $ = __doctest.require ('sanctuary-def'); + var type = __doctest.require ('sanctuary-type-identifiers'); + var S = (function() { + var S = __doctest.require ('sanctuary'); + var EitherType = $.BinaryType + ('sanctuary-either/Either') + ('') + (function(x) { return type (x) === Either['@@type']; }) + (function(e) { return e.isLeft ? [e.value] : []; }) + (function(e) { return e.isLeft ? [] : [e.value]; }); + var env = Z.concat (S.env, + [$.TypeClass, EitherType ($.Unknown) ($.Unknown)]); + return S.create ({checkTypes: true, env: env}); + } ()); + } + + var Either = {}; + + var Left$prototype = { + /* eslint-disable key-spacing */ + 'constructor': Either, + 'isLeft': true, + 'isRight': false, + '@@show': Left$prototype$show, + 'fantasy-land/map': Left$prototype$map, + 'fantasy-land/bimap': Left$prototype$bimap, + 'fantasy-land/ap': Left$prototype$ap, + 'fantasy-land/chain': Left$prototype$chain, + 'fantasy-land/alt': Left$prototype$alt, + 'fantasy-land/reduce': Left$prototype$reduce, + 'fantasy-land/traverse': Left$prototype$traverse, + 'fantasy-land/extend': Left$prototype$extend + /* eslint-enable key-spacing */ + }; + + var Right$prototype = { + /* eslint-disable key-spacing */ + 'constructor': Either, + 'isLeft': false, + 'isRight': true, + '@@show': Right$prototype$show, + 'fantasy-land/map': Right$prototype$map, + 'fantasy-land/bimap': Right$prototype$bimap, + 'fantasy-land/ap': Right$prototype$ap, + 'fantasy-land/chain': Right$prototype$chain, + 'fantasy-land/alt': Right$prototype$alt, + 'fantasy-land/reduce': Right$prototype$reduce, + 'fantasy-land/traverse': Right$prototype$traverse, + 'fantasy-land/extend': Right$prototype$extend + /* eslint-enable key-spacing */ + }; + + var util = + typeof module === 'object' && typeof module.exports === 'object' ? + require ('util') : + /* istanbul ignore next */ {}; + var inspect = + util.inspect != null && typeof util.inspect.custom === 'symbol' ? + /* istanbul ignore next */ util.inspect.custom : + /* istanbul ignore next */ 'inspect'; + Left$prototype[inspect] = Left$prototype$show; + Right$prototype[inspect] = Right$prototype$show; + + //. `Either a b` satisfies the following [Fantasy Land][] specifications: + //. + //. ```javascript + //. > const Useless = require ('sanctuary-useless') + //. + //. > S.map (k => k + ' '.repeat (16 - k.length) + + //. . (Z[k].test (Right (Useless)) ? '\u2705 ' : + //. . Z[k].test (Right (['foo'])) ? '\u2705 * ' : + //. . /* otherwise */ '\u274C ')) + //. . (S.keys (Z.filter ($.test ([]) ($.TypeClass), Z))) + //. [ 'Setoid ✅ * ', // if ‘a’ and ‘b’ satisfy Setoid + //. . 'Ord ✅ * ', // if ‘a’ and ‘b’ satisfy Ord + //. . 'Semigroupoid ❌ ', + //. . 'Category ❌ ', + //. . 'Semigroup ✅ * ', // if ‘a’ and ‘b’ satisfy Semigroup + //. . 'Monoid ❌ ', + //. . 'Group ❌ ', + //. . 'Filterable ❌ ', + //. . 'Functor ✅ ', + //. . 'Bifunctor ✅ ', + //. . 'Profunctor ❌ ', + //. . 'Apply ✅ ', + //. . 'Applicative ✅ ', + //. . 'Chain ✅ ', + //. . 'ChainRec ✅ ', + //. . 'Monad ✅ ', + //. . 'Alt ✅ ', + //. . 'Plus ❌ ', + //. . 'Alternative ❌ ', + //. . 'Foldable ✅ ', + //. . 'Traversable ✅ ', + //. . 'Extend ✅ ', + //. . 'Comonad ❌ ', + //. . 'Contravariant ❌ ' ] + //. ``` + + //# Either :: TypeRep Either + //. + //. Either [type representative][]. + + //# Either.Left :: a -> Either a b + //. + //. Constructs a value of type `Either a b` from a value of type `a`. + //. + //. ```javascript + //. > Left ('sqrt undefined for -1') + //. Left ('sqrt undefined for -1') + //. ``` + var Left = Either.Left = function(value) { + var left = Object.create (Left$prototype); + if (Z.Setoid.test (value)) { + left['fantasy-land/equals'] = Left$prototype$equals; + if (Z.Ord.test (value)) { + left['fantasy-land/lte'] = Left$prototype$lte; + } + } + if (Z.Semigroup.test (value)) { + left['fantasy-land/concat'] = Left$prototype$concat; + } + left.value = value; + return left; + }; + + //# Either.Right :: b -> Either a b + //. + //. Constructs a value of type `Either a b` from a value of type `b`. + //. + //. ```javascript + //. > Right (42) + //. Right (42) + //. ``` + var Right = Either.Right = function Right(value) { + var right = Object.create (Right$prototype); + if (Z.Setoid.test (value)) { + right['fantasy-land/equals'] = Right$prototype$equals; + if (Z.Ord.test (value)) { + right['fantasy-land/lte'] = Right$prototype$lte; + } + } + if (Z.Semigroup.test (value)) { + right['fantasy-land/concat'] = Right$prototype$concat; + } + right.value = value; + return right; + }; + + //# Either.@@type :: String + //. + //. Either [type identifier][]. + //. + //. ```javascript + //. > type (Right (42)) + //. 'sanctuary-either/Either@1' + //. + //. > type.parse (type (Right (42))) + //. {namespace: 'sanctuary-either', name: 'Either', version: 1} + //. ``` + Either['@@type'] = 'sanctuary-either/Either@1'; + + //# Either.fantasy-land/of :: b -> Either a b + //. + //. - `of (Either) (x)` is equivalent to `Right (x)` + //. + //. ```javascript + //. > S.of (Either) (42) + //. Right (42) + //. ``` + Either['fantasy-land/of'] = Right; + + function next(x) { return {tag: next, value: x}; } + function done(x) { return {tag: done, value: x}; } + + //# Either.fantasy-land/chainRec :: ((a -> c, b -> c, a) -> Either d c, a) -> Either d b + //. + //. ```javascript + //. > Z.chainRec ( + //. . Either, + //. . (next, done, x) => + //. . x <= 1 ? Left ('!!') : Right (x >= 1000 ? done (x) : next (x * x)), + //. . 1 + //. . ) + //. Left ('!!') + //. + //. > Z.chainRec ( + //. . Either, + //. . (next, done, x) => + //. . x <= 1 ? Left ('!!') : Right (x >= 1000 ? done (x) : next (x * x)), + //. . 2 + //. . ) + //. Right (65536) + //. ``` + Either['fantasy-land/chainRec'] = function(f, x) { + var r = next (x); + while (r.tag === next) { + var either = f (next, done, r.value); + if (either.isLeft) return either; + r = either.value; + } + return Right (r.value); + }; + + //# Either#@@show :: (Showable a, Showable b) => Either a b ~> () -> String + //. + //. - `show (Left (x))` is equivalent to `'Left (' + show (x) + ')'` + //. - `show (Right (x))` is equivalent to `'Right (' + show (x) + ')'` + //. + //. ```javascript + //. > show (Left ('sqrt undefined for -1')) + //. 'Left ("sqrt undefined for -1")' + //. + //. > show (Right ([1, 2, 3])) + //. 'Right ([1, 2, 3])' + //. ``` + function Left$prototype$show() { + return 'Left (' + show (this.value) + ')'; + } + function Right$prototype$show() { + return 'Right (' + show (this.value) + ')'; + } + + //# Either#fantasy-land/equals :: (Setoid a, Setoid b) => Either a b ~> Either a b -> Boolean + //. + //. - `Left (x)` is equal to `Left (y)` [iff][] `x` is equal to `y` + //. according to [`Z.equals`][] + //. - `Right (x)` is equal to `Right (y)` [iff][] `x` is equal to `y` + //. according to [`Z.equals`][] + //. - `Left (x)` is never equal to `Right (y)` + //. + //. ```javascript + //. > S.equals (Left ([1, 2, 3])) (Left ([1, 2, 3])) + //. true + //. + //. > S.equals (Right ([1, 2, 3])) (Right ([1, 2, 3])) + //. true + //. + //. > S.equals (Left ([1, 2, 3])) (Right ([1, 2, 3])) + //. false + //. ``` + function Left$prototype$equals(other) { + return other.isLeft && Z.equals (this.value, other.value); + } + function Right$prototype$equals(other) { + return other.isRight && Z.equals (this.value, other.value); + } + + //# Either#fantasy-land/lte :: (Ord a, Ord b) => Either a b ~> Either a b -> Boolean + //. + //. - `Left (x)` is less than or equal to `Left (y)` [iff][] `x` is less + //. than or equal to `y` according to [`Z.lte`][] + //. - `Right (x)` is less than or equal to `Right (y)` [iff][] `x` is less + //. than or equal to `y` according to [`Z.lte`][] + //. - `Left (x)` is always less than `Right (y)` + //. + //. ```javascript + //. > S.filter (S.lte (Left (1))) ([Left (0), Left (1), Left (2)]) + //. [Left (0), Left (1)] + //. + //. > S.filter (S.lte (Right (1))) ([Right (0), Right (1), Right (2)]) + //. [Right (0), Right (1)] + //. + //. > S.filter (S.lte (Left (1))) ([Right (0), Right (1), Right (2)]) + //. [] + //. + //. > S.filter (S.lte (Right (1))) ([Left (0), Left (1), Left (2)]) + //. [Left (0), Left (1), Left (2)] + //. ``` + function Left$prototype$lte(other) { + return other.isRight || Z.lte (this.value, other.value); + } + function Right$prototype$lte(other) { + return other.isRight && Z.lte (this.value, other.value); + } + + //# Either#fantasy-land/concat :: (Semigroup a, Semigroup b) => Either a b ~> Either a b -> Either a b + //. + //. - `concat (Left (x)) (Left (y))` is equivalent to + //. `Left (concat (x) (y))` + //. - `concat (Right (x)) (Right (y))` is equivalent to + //. `Right (concat (x) (y))` + //. - `concat (Left (x)) (Right (y))` is equivalent to `Right (y)` + //. - `concat (Right (x)) (Left (y))` is equivalent to `Right (x)` + //. + //. ```javascript + //. > S.concat (Left ('abc')) (Left ('def')) + //. Left ('abcdef') + //. + //. > S.concat (Right ([1, 2, 3])) (Right ([4, 5, 6])) + //. Right ([1, 2, 3, 4, 5, 6]) + //. + //. > S.concat (Left ('abc')) (Right ([1, 2, 3])) + //. Right ([1, 2, 3]) + //. + //. > S.concat (Right ([1, 2, 3])) (Left ('abc')) + //. Right ([1, 2, 3]) + //. ``` + function Left$prototype$concat(other) { + return other.isLeft ? Left (Z.concat (this.value, other.value)) : other; + } + function Right$prototype$concat(other) { + return other.isRight ? Right (Z.concat (this.value, other.value)) : this; + } + + //# Either#fantasy-land/map :: Either a b ~> (b -> c) -> Either a c + //. + //. - `map (f) (Left (x))` is equivalent to `Left (x)` + //. - `map (f) (Right (x))` is equivalent to `Right (f (x))` + //. + //. ```javascript + //. > S.map (S.add (1)) (Left ('sqrt undefined for -1')) + //. Left ('sqrt undefined for -1') + //. + //. > S.map (S.add (1)) (Right (99)) + //. Right (100) + //. ``` + function Left$prototype$map(f) { + return this; + } + function Right$prototype$map(f) { + return Right (f (this.value)); + } + + //# Either#fantasy-land/bimap :: Either a c ~> (a -> b, c -> d) -> Either b d + //. + //. - `bimap (f) (g) (Left (x))` is equivalent to `Left (f (x))` + //. - `bimap (f) (g) (Right (x))` is equivalent to `Right (g (x))` + //. + //. ```javascript + //. > S.bimap (S.toUpper) (S.add (1)) (Left ('abc')) + //. Left ('ABC') + //. + //. > S.bimap (S.toUpper) (S.add (1)) (Right (99)) + //. Right (100) + //. ``` + function Left$prototype$bimap(f, g) { + return Left (f (this.value)); + } + function Right$prototype$bimap(f, g) { + return Right (g (this.value)); + } + + //# Either#fantasy-land/ap :: Either a b ~> Either a (b -> c) -> Either a c + //. + //. - `ap (Left (x)) (Left (y))` is equivalent to `Left (x)` + //. - `ap (Left (x)) (Right (y))` is equivalent to `Left (x)` + //. - `ap (Right (f)) (Left (x))` is equivalent to `Left (x)` + //. - `ap (Right (f)) (Right (x))` is equivalent to `Right (f (x))` + //. + //. ```javascript + //. > S.ap (Left ('div undefined for 0')) (Left ('sqrt undefined for -1')) + //. Left ('div undefined for 0') + //. + //. > S.ap (Left ('div undefined for 0')) (Right (99)) + //. Left ('div undefined for 0') + //. + //. > S.ap (Right (S.add (1))) (Left ('sqrt undefined for -1')) + //. Left ('sqrt undefined for -1') + //. + //. > S.ap (Right (S.add (1))) (Right (99)) + //. Right (100) + //. ``` + function Left$prototype$ap(other) { + return other.isLeft ? other : this; + } + function Right$prototype$ap(other) { + return other.isLeft ? other : Right (other.value (this.value)); + } + + //# Either#fantasy-land/chain :: Either a b ~> (b -> Either a c) -> Either a c + //. + //. - `chain (f) (Left (x))` is equivalent to `Left (x)` + //. - `chain (f) (Right (x))` is equivalent to `f (x)` + //. + //. ```javascript + //. > const sqrt = n => n < 0 ? Left ('sqrt undefined for ' + show (n)) + //. . : Right (Math.sqrt (n)) + //. + //. > S.chain (sqrt) (Left ('div undefined for 0')) + //. Left ('div undefined for 0') + //. + //. > S.chain (sqrt) (Right (-1)) + //. Left ('sqrt undefined for -1') + //. + //. > S.chain (sqrt) (Right (25)) + //. Right (5) + //. ``` + function Left$prototype$chain(f) { + return this; + } + function Right$prototype$chain(f) { + return f (this.value); + } + + //# Either#fantasy-land/alt :: Either a b ~> Either a b -> Either a b + //. + //. - `alt (Left (x)) (Left (y))` is equivalent to `Left (y)` + //. - `alt (Left (x)) (Right (y))` is equivalent to `Right (y)` + //. - `alt (Right (x)) (Left (y))` is equivalent to `Right (x)` + //. - `alt (Right (x)) (Right (y))` is equivalent to `Right (x)` + //. + //. ```javascript + //. > S.alt (Left ('A')) (Left ('B')) + //. Left ('B') + //. + //. > S.alt (Left ('C')) (Right (1)) + //. Right (1) + //. + //. > S.alt (Right (2)) (Left ('D')) + //. Right (2) + //. + //. > S.alt (Right (3)) (Right (4)) + //. Right (3) + //. ``` + function Left$prototype$alt(other) { + return other; + } + function Right$prototype$alt(other) { + return this; + } + + //# Either#fantasy-land/reduce :: Either a b ~> ((c, b) -> c, c) -> c + //. + //. - `reduce (f) (x) (Left (y))` is equivalent to `x` + //. - `reduce (f) (x) (Right (y))` is equivalent to `f (x) (y)` + //. + //. ```javascript + //. > S.reduce (S.concat) ([1]) (Left ('sqrt undefined for -1')) + //. [1] + //. + //. > S.reduce (S.concat) ([1]) (Right ([2])) + //. [1, 2] + //. ``` + function Left$prototype$reduce(f, x) { + return x; + } + function Right$prototype$reduce(f, x) { + return f (x, this.value); + } + + //# Either#fantasy-land/traverse :: Applicative f => Either a b ~> (TypeRep f, b -> f c) -> f (Either a c) + //. + //. - `traverse (A) (f) (Left (x))` is equivalent to `of (A) (Left (x))` + //. - `traverse (A) (f) (Right (x))` is equivalent to `map (Right) (f (x))` + //. + //. ```javascript + //. > S.traverse (Array) (S.words) (Left ('sqrt undefined for -1')) + //. [Left ('sqrt undefined for -1')] + //. + //. > S.traverse (Array) (S.words) (Right ('foo bar baz')) + //. [Right ('foo'), Right ('bar'), Right ('baz')] + //. ``` + function Left$prototype$traverse(typeRep, f) { + return Z.of (typeRep, this); + } + function Right$prototype$traverse(typeRep, f) { + return Z.map (Right, f (this.value)); + } + + //# Either#fantasy-land/extend :: Either a b ~> (Either a b -> c) -> Either a c + //. + //. - `extend (f) (Left (x))` is equivalent to `Left (x)` + //. - `extend (f) (Right (x))` is equivalent to `Right (f (Right (x)))` + //. + //. ```javascript + //. > S.extend (S.reduce (S.add) (1)) (Left ('sqrt undefined for -1')) + //. Left ('sqrt undefined for -1') + //. + //. > S.extend (S.reduce (S.add) (1)) (Right (99)) + //. Right (100) + //. ``` + function Left$prototype$extend(f) { + return this; + } + function Right$prototype$extend(f) { + return Right (f (this)); + } + + return Either; + +})); + +//. [Fantasy Land]: v:fantasyland/fantasy-land +//. [`Z.equals`]: v:sanctuary-js/sanctuary-type-classes#equals +//. [`Z.lte`]: v:sanctuary-js/sanctuary-type-classes#lte +//. [iff]: https://en.wikipedia.org/wiki/If_and_only_if +//. [type identifier]: v:sanctuary-js/sanctuary-type-identifiers +//. [type representative]: v:fantasyland/fantasy-land#type-representatives diff --git a/package.json b/package.json new file mode 100644 index 0000000..922cd2a --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "sanctuary-either", + "version": "0.0.0", + "description": "Fantasy Land -compliant Either type", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/sanctuary-js/sanctuary-either.git" + }, + "files": [ + "/LICENSE", + "/README.md", + "/index.js", + "/package.json" + ], + "dependencies": { + "sanctuary-show": "1.0.x", + "sanctuary-type-classes": "9.0.0" + }, + "devDependencies": { + "fantasy-land": "3.5.0", + "fantasy-laws": "1.0.x", + "jsverify": "0.8.x", + "sanctuary": "0.14.1", + "sanctuary-def": "0.17.x", + "sanctuary-identity": "1.0.x", + "sanctuary-scripts": "2.0.x", + "sanctuary-type-identifiers": "2.0.1", + "sanctuary-useless": "1.0.x" + }, + "scripts": { + "doctest": "sanctuary-doctest", + "lint": "sanctuary-lint", + "release": "sanctuary-release", + "test": "npm run lint && sanctuary-test && npm run doctest" + } +} diff --git a/test/.eslintrc.json b/test/.eslintrc.json new file mode 100644 index 0000000..249d948 --- /dev/null +++ b/test/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "root": true, + "extends": ["../node_modules/sanctuary-style/eslint-es6.json"], + "env": {"node": true}, + "globals": {"suite": false, "test": false} +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..57e4f56 --- /dev/null +++ b/test/index.js @@ -0,0 +1,411 @@ +'use strict'; + +const assert = require ('assert'); + +const laws = require ('fantasy-laws'); +const jsc = require ('jsverify'); +const Identity = require ('sanctuary-identity'); +const show = require ('sanctuary-show'); +const Z = require ('sanctuary-type-classes'); +const type = require ('sanctuary-type-identifiers'); +const Useless = require ('sanctuary-useless'); + +const Either = require ('..'); + + +const {Left, Right} = Either; + + +// EitherArb :: Arbitrary a -> Arbitrary b -> Arbitrary (Either a b) +const EitherArb = arbL => arbR => + jsc.oneof (arbL.smap (Left, left => left.value, show), + arbR.smap (Right, right => right.value, show)); + +// IdentityArb :: Arbitrary a -> Arbitrary (Identity a) +const IdentityArb = arb => arb.smap (Identity, Z.extract, show); + +// head :: Array a -> Either String a +const head = xs => + xs.length === 0 ? Left ('head undefined for ' + show (xs)) : Right (xs[0]); + +// sqrt :: Number -> Either String Number +const sqrt = n => + n < 0 ? Left ('sqrt undefined for ' + show (n)) : Right (Math.sqrt (n)); + +// testLaws :: Object -> Object -> Undefined +const testLaws = laws => arbs => { + (Object.keys (laws)).forEach (name => { + test (name.replace (/[A-Z]/g, c => ' ' + c.toLowerCase ()), + laws[name] (...arbs[name])); + }); +}; + +// eq :: a -> b -> Undefined ! +function eq(actual) { + assert.strictEqual (arguments.length, eq.length); + return function eq$1(expected) { + assert.strictEqual (arguments.length, eq$1.length); + assert.strictEqual (show (actual), show (expected)); + assert.strictEqual (Z.equals (actual, expected), true); + }; +} + + +suite ('Either', () => { + + test ('metadata', () => { + eq (typeof Left) ('function'); + eq (typeof Right) ('function'); + eq (Left.length) (1); + eq (Right.length) (1); + }); + + test ('tags', () => { + const left = Left (0); + const right = Right (0); + eq (left.isLeft) (true); + eq (left.isRight) (false); + eq (right.isLeft) (false); + eq (right.isRight) (true); + }); + + test ('@@type', () => { + eq (type (Left (0))) ('sanctuary-either/Either@1'); + eq (type (Right (0))) ('sanctuary-either/Either@1'); + eq (type.parse (type (Right (0)))) + ({namespace: 'sanctuary-either', name: 'Either', version: 1}); + }); + + test ('@@show', () => { + eq (show (Left (['foo', 'bar', 'baz']))) + ('Left (["foo", "bar", "baz"])'); + eq (show (Right (['foo', 'bar', 'baz']))) + ('Right (["foo", "bar", "baz"])'); + eq (show (Left (Right (Left (Right (-0)))))) + ('Left (Right (Left (Right (-0))))'); + }); + +}); + +suite ('type-class predicates', () => { + + test ('Setoid', () => { + eq (Z.Setoid.test (Left (Useless))) (false); + eq (Z.Setoid.test (Left (/(?:)/))) (true); + eq (Z.Setoid.test (Right (Useless))) (false); + eq (Z.Setoid.test (Right (/(?:)/))) (true); + }); + + test ('Ord', () => { + eq (Z.Ord.test (Left (Useless))) (false); + eq (Z.Ord.test (Left (/(?:)/))) (false); + eq (Z.Ord.test (Left (0))) (true); + eq (Z.Ord.test (Right (Useless))) (false); + eq (Z.Ord.test (Right (/(?:)/))) (false); + eq (Z.Ord.test (Right (0))) (true); + }); + + test ('Semigroupoid', () => { + eq (Z.Semigroupoid.test (Left ([]))) (false); + eq (Z.Semigroupoid.test (Right ([]))) (false); + }); + + test ('Category', () => { + eq (Z.Category.test (Left ([]))) (false); + eq (Z.Category.test (Right ([]))) (false); + }); + + test ('Semigroup', () => { + eq (Z.Semigroup.test (Left (Useless))) (false); + eq (Z.Semigroup.test (Left (0))) (false); + eq (Z.Semigroup.test (Left ([]))) (true); + eq (Z.Semigroup.test (Right (Useless))) (false); + eq (Z.Semigroup.test (Right (0))) (false); + eq (Z.Semigroup.test (Right ([]))) (true); + }); + + test ('Monoid', () => { + eq (Z.Monoid.test (Left ([]))) (false); + eq (Z.Monoid.test (Right ([]))) (false); + }); + + test ('Group', () => { + eq (Z.Group.test (Left ([]))) (false); + eq (Z.Group.test (Right ([]))) (false); + }); + + test ('Filterable', () => { + eq (Z.Filterable.test (Left ([]))) (false); + eq (Z.Filterable.test (Right ([]))) (false); + }); + + test ('Functor', () => { + eq (Z.Functor.test (Left (Useless))) (true); + eq (Z.Functor.test (Right (Useless))) (true); + }); + + test ('Bifunctor', () => { + eq (Z.Bifunctor.test (Left (Useless))) (true); + eq (Z.Bifunctor.test (Right (Useless))) (true); + }); + + test ('Profunctor', () => { + eq (Z.Profunctor.test (Left (Math.sqrt))) (false); + eq (Z.Profunctor.test (Right (Math.sqrt))) (false); + }); + + test ('Apply', () => { + eq (Z.Apply.test (Left (Useless))) (true); + eq (Z.Apply.test (Right (Useless))) (true); + }); + + test ('Applicative', () => { + eq (Z.Applicative.test (Left (Useless))) (true); + eq (Z.Applicative.test (Right (Useless))) (true); + }); + + test ('Chain', () => { + eq (Z.Chain.test (Left (Useless))) (true); + eq (Z.Chain.test (Right (Useless))) (true); + }); + + test ('ChainRec', () => { + eq (Z.ChainRec.test (Left (Useless))) (true); + eq (Z.ChainRec.test (Right (Useless))) (true); + }); + + test ('Monad', () => { + eq (Z.Monad.test (Left (Useless))) (true); + eq (Z.Monad.test (Right (Useless))) (true); + }); + + test ('Alt', () => { + eq (Z.Alt.test (Left (Useless))) (true); + eq (Z.Alt.test (Right (Useless))) (true); + }); + + test ('Plus', () => { + eq (Z.Plus.test (Left ([]))) (false); + eq (Z.Plus.test (Right ([]))) (false); + }); + + test ('Alternative', () => { + eq (Z.Alternative.test (Left ([]))) (false); + eq (Z.Alternative.test (Right ([]))) (false); + }); + + test ('Foldable', () => { + eq (Z.Foldable.test (Left (Useless))) (true); + eq (Z.Foldable.test (Right (Useless))) (true); + }); + + test ('Traversable', () => { + eq (Z.Traversable.test (Left (Useless))) (true); + eq (Z.Traversable.test (Right (Useless))) (true); + }); + + test ('Extend', () => { + eq (Z.Extend.test (Left (Useless))) (true); + eq (Z.Extend.test (Right (Useless))) (true); + }); + + test ('Comonad', () => { + eq (Z.Comonad.test (Left (Identity (0)))) (false); + eq (Z.Comonad.test (Right (Identity (0)))) (false); + }); + + test ('Contravariant', () => { + eq (Z.Contravariant.test (Left (Math.sqrt))) (false); + eq (Z.Contravariant.test (Right (Math.sqrt))) (false); + }); + +}); + +suite ('Setoid laws', () => { + testLaws (laws.Setoid) ({ + reflexivity: [ + EitherArb (jsc.string) (jsc.falsy), + ], + symmetry: [ + EitherArb (jsc.bool) (jsc.bool), + EitherArb (jsc.bool) (jsc.bool), + ], + transitivity: [ + EitherArb (jsc.bool) (jsc.bool), + EitherArb (jsc.bool) (jsc.bool), + EitherArb (jsc.bool) (jsc.bool), + ], + }); +}); + +suite ('Ord laws', () => { + testLaws (laws.Ord) ({ + totality: [ + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + ], + antisymmetry: [ + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + ], + transitivity: [ + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Semigroup laws', () => { + testLaws (laws.Semigroup (Z.equals)) ({ + associativity: [ + EitherArb (jsc.string) (jsc.string), + EitherArb (jsc.string) (jsc.string), + EitherArb (jsc.string) (jsc.string), + ], + }); +}); + +suite ('Functor laws', () => { + testLaws (laws.Functor (Z.equals)) ({ + identity: [ + EitherArb (jsc.string) (jsc.number), + ], + composition: [ + EitherArb (jsc.string) (jsc.number), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], + }); +}); + +suite ('Bifunctor laws', () => { + testLaws (laws.Bifunctor (Z.equals)) ({ + identity: [ + EitherArb (jsc.string) (jsc.number), + ], + composition: [ + EitherArb (jsc.string) (jsc.number), + jsc.constant (Math.sqrt), + jsc.constant (s => s.length), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], + }); +}); + +suite ('Apply laws', () => { + testLaws (laws.Apply (Z.equals)) ({ + composition: [ + EitherArb (jsc.string) (jsc.constant (Math.sqrt)), + EitherArb (jsc.string) (jsc.constant (Math.abs)), + EitherArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Applicative laws', () => { + testLaws (laws.Applicative (Z.equals, Either)) ({ + identity: [ + EitherArb (jsc.string) (jsc.number), + ], + homomorphism: [ + jsc.constant (Math.abs), + jsc.number, + ], + interchange: [ + EitherArb (jsc.string) (jsc.constant (Math.abs)), + jsc.number, + ], + }); +}); + +suite ('Chain laws', () => { + testLaws (laws.Chain (Z.equals)) ({ + associativity: [ + EitherArb (jsc.string) (jsc.array (jsc.number)), + jsc.constant (head), + jsc.constant (sqrt), + ], + }); +}); + +suite ('ChainRec laws', () => { + testLaws (laws.ChainRec (Z.equals, Either)) ({ + equivalence: [ + jsc.constant (x => x >= 1000), + jsc.constant (x => x <= 1 ? Left (show (x) + ' <= 1') : Right (x * x)), + jsc.constant (Right), + jsc.integer, + ], + }); +}); + +suite ('Monad laws', () => { + testLaws (laws.Monad (Z.equals, Either)) ({ + leftIdentity: [ + jsc.constant (sqrt), + jsc.number, + ], + rightIdentity: [ + EitherArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Alt laws', () => { + testLaws (laws.Alt (Z.equals)) ({ + associativity: [ + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + ], + distributivity: [ + EitherArb (jsc.string) (jsc.number), + EitherArb (jsc.string) (jsc.number), + jsc.constant (Math.sqrt), + ], + }); +}); + +suite ('Foldable laws', () => { + testLaws (laws.Foldable (Z.equals)) ({ + associativity: [ + jsc.constant ((x, y) => x + y), + jsc.number, + EitherArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Traversable laws', () => { + testLaws (laws.Traversable (Z.equals)) ({ + naturality: [ + jsc.constant (Identity), + jsc.constant (Array), + jsc.constant (identity => [Z.extract (identity)]), + EitherArb (jsc.string) (IdentityArb (jsc.number)), + ], + identity: [ + jsc.constant (Identity), + EitherArb (jsc.string) (jsc.number), + ], + composition: [ + jsc.constant (Identity), + jsc.constant (Either), + EitherArb (jsc.string) + (IdentityArb (EitherArb (jsc.string) (jsc.number))), + ], + }); +}); + +suite ('Extend laws', () => { + testLaws (laws.Extend (Z.equals)) ({ + associativity: [ + EitherArb (jsc.string) (jsc.integer), + jsc.constant (either => Z.reduce ((x, y) => x + y, 1, either)), + jsc.constant (either => Z.reduce ((x, y) => y * y, 1, either)), + ], + }); +}); diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 0000000..5efaf24 --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1 @@ +--ui tdd