From 5dd6c6e141da4ccb39bc5a35149ea6dc71966ebb Mon Sep 17 00:00:00 2001 From: David Chambers Date: Sun, 19 Mar 2017 15:30:24 +0100 Subject: [PATCH] everything --- .circleci/config.yml | 36 +++++ .config | 2 + .eslintrc.json | 16 ++ .gitignore | 2 + .npmrc | 1 + CONTRIBUTING.md | 27 ++++ LICENSE | 24 +++ index.js | 340 ++++++++++++++++++++++++++++++++++++++++ package.json | 36 +++++ test/.eslintrc.json | 6 + test/index.js | 359 +++++++++++++++++++++++++++++++++++++++++++ test/mocha.opts | 1 + 12 files changed, 850 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..773ae03 --- /dev/null +++ b/.config @@ -0,0 +1,2 @@ +repo-owner = sanctuary-js +repo-name = sanctuary-identity diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..6c6658b --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "root": true, + "extends": ["./node_modules/sanctuary-style/eslint-es3.json"], + "overrides": [ + { + "files": ["*.md"], + "globals": {"$": false, "Identity": false, "S": false, "Z": false, "show": false, "type": false} + }, + { + "files": ["index.js"], + "rules": { + "no-unused-vars": ["error", {"varsIgnorePattern": "^S$"}] + } + } + ] +} 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..1a41c06 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2018 Sanctuary + +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..829baf9 --- /dev/null +++ b/index.js @@ -0,0 +1,340 @@ +/* ___________________ + / /\ + / / \ + /_____ ______/ / + \ / /\ \ / + \__/ / \_____\/ + / / / + / / / + ______/ /___/_ + / /\ + / / \ + /__________________/ / + \ \ / + \__________________*/ + +//. Fantasy Land +//. +//. # sanctuary-identity +//. +//. Identity is the simplest container type: a value of type `Identity a` +//. always contains exactly one value, of type `a`. + +(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.sanctuaryIdentity = 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 IdentityType = $.UnaryType + ('sanctuary-identity/Identity') + ('') + (function(x) { return type (x) === Identity['@@type']; }) + (function(i) { return [i.value]; }); + var env = Z.concat (S.env, [$.TypeClass, IdentityType ($.Unknown)]); + return S.create ({checkTypes: true, env: env}); + } ()); + } + + var prototype = { + /* eslint-disable key-spacing */ + 'constructor': Identity, + '@@show': Identity$prototype$show, + 'fantasy-land/map': Identity$prototype$map, + 'fantasy-land/ap': Identity$prototype$ap, + 'fantasy-land/chain': Identity$prototype$chain, + 'fantasy-land/reduce': Identity$prototype$reduce, + 'fantasy-land/traverse': Identity$prototype$traverse, + 'fantasy-land/extend': Identity$prototype$extend, + 'fantasy-land/extract': Identity$prototype$extract + /* eslint-enable key-spacing */ + }; + + var util = + typeof module === 'object' && typeof module.exports === 'object' ? + require ('util') : + /* istanbul ignore next */ {}; + prototype[ + util.inspect != null && typeof util.inspect.custom === 'symbol' ? + /* istanbul ignore next */ util.inspect.custom : + /* istanbul ignore next */ 'inspect' + ] = Identity$prototype$show; + + //. `Identity a` satisfies the following [Fantasy Land][] specifications: + //. + //. ```javascript + //. > const Useless = require ('sanctuary-useless') + //. + //. > S.map (k => k + ' '.repeat (16 - k.length) + + //. . (Z[k].test (Identity (Useless)) ? '\u2705 ' : + //. . Z[k].test (Identity (['foo'])) ? '\u2705 * ' : + //. . /* otherwise */ '\u274C ')) + //. . (S.keys (Z.filter ($.test ([]) ($.TypeClass), Z))) + //. [ 'Setoid ✅ * ', // if ‘a’ satisfies Setoid + //. . 'Ord ✅ * ', // if ‘a’ satisfies Ord + //. . 'Semigroupoid ❌ ', + //. . 'Category ❌ ', + //. . 'Semigroup ✅ * ', // if ‘a’ satisfies Semigroup + //. . 'Monoid ❌ ', + //. . 'Group ❌ ', + //. . 'Filterable ❌ ', + //. . 'Functor ✅ ', + //. . 'Bifunctor ❌ ', + //. . 'Profunctor ❌ ', + //. . 'Apply ✅ ', + //. . 'Applicative ✅ ', + //. . 'Chain ✅ ', + //. . 'ChainRec ✅ ', + //. . 'Monad ✅ ', + //. . 'Alt ❌ ', + //. . 'Plus ❌ ', + //. . 'Alternative ❌ ', + //. . 'Foldable ✅ ', + //. . 'Traversable ✅ ', + //. . 'Extend ✅ ', + //. . 'Comonad ✅ ', + //. . 'Contravariant ❌ ' ] + //. ``` + + //# Identity :: a -> Identity a + //. + //. Identity's sole data constructor. Additionally, it serves as the + //. Identity [type representative][]. + //. + //. ```javascript + //. > Identity (42) + //. Identity (42) + //. ``` + function Identity(value) { + var identity = Object.create (prototype); + if (Z.Setoid.test (value)) { + identity['fantasy-land/equals'] = Identity$prototype$equals; + if (Z.Ord.test (value)) { + identity['fantasy-land/lte'] = Identity$prototype$lte; + } + } + if (Z.Semigroup.test (value)) { + identity['fantasy-land/concat'] = Identity$prototype$concat; + } + identity.value = value; + return identity; + } + + //# Identity.@@type :: String + //. + //. Identity [type identifier][]. + //. + //. ```javascript + //. > type (Identity (42)) + //. 'sanctuary-identity/Identity@1' + //. + //. > type.parse (type (Identity (42))) + //. {namespace: 'sanctuary-identity', name: 'Identity', version: 1} + //. ``` + Identity['@@type'] = 'sanctuary-identity/Identity@1'; + + //# Identity.fantasy-land/of :: a -> Identity a + //. + //. `of (Identity) (x)` is equivalent to `Identity (x)`. + //. + //. ```javascript + //. > S.of (Identity) (42) + //. Identity (42) + //. ``` + Identity['fantasy-land/of'] = Identity; + + function next(x) { return {tag: next, value: x}; } + function done(x) { return {tag: done, value: x}; } + + //# Identity.fantasy-land/chainRec :: ((a -> c, b -> c, a) -> Identity c, a) -> Identity b + //. + //. ```javascript + //. > Z.chainRec ( + //. . Identity, + //. . (next, done, x) => Identity (x >= 0 ? done (x * x) : next (x + 1)), + //. . 8 + //. . ) + //. Identity (64) + //. + //. > Z.chainRec ( + //. . Identity, + //. . (next, done, x) => Identity (x >= 0 ? done (x * x) : next (x + 1)), + //. . -8 + //. . ) + //. Identity (0) + //. ``` + Identity['fantasy-land/chainRec'] = function(f, x) { + var r = next (x); + while (r.tag === next) r = (f (next, done, r.value)).value; + return Identity (r.value); + }; + + //# Identity#@@show :: Showable a => Identity a ~> () -> String + //. + //. `show (Identity (x))` is equivalent to `'Identity (' + show (x) + ')'`. + //. + //. ```javascript + //. > show (Identity (['foo', 'bar', 'baz'])) + //. 'Identity (["foo", "bar", "baz"])' + //. ``` + function Identity$prototype$show() { + return 'Identity (' + show (this.value) + ')'; + } + + //# Identity#fantasy-land/equals :: Setoid a => Identity a ~> Identity a -> Boolean + //. + //. `Identity (x)` is equal to `Identity (y)` [iff][] `x` is equal to `y` + //. according to [`Z.equals`][]. + //. + //. ```javascript + //. > S.equals (Identity ([1, 2, 3])) (Identity ([1, 2, 3])) + //. true + //. + //. > S.equals (Identity ([1, 2, 3])) (Identity ([3, 2, 1])) + //. false + //. ``` + function Identity$prototype$equals(other) { + return Z.equals (this.value, other.value); + } + + //# Identity#fantasy-land/lte :: Ord a => Identity a ~> Identity a -> Boolean + //. + //. `Identity (x)` is less than or equal to `Identity (y)` [iff][] `x` is + //. less than or equal to `y` according to [`Z.lte`][]. + //. + //. ```javascript + //. > S.filter (S.lte (Identity (1))) + //. . ([Identity (0), Identity (1), Identity (2)]) + //. [Identity (0), Identity (1)] + //. ``` + function Identity$prototype$lte(other) { + return Z.lte (this.value, other.value); + } + + //# Identity#fantasy-land/concat :: Semigroup a => Identity a ~> Identity a -> Identity a + //. + //. `concat (Identity (x)) (Identity (y))` is equivalent to + //. `Identity (concat (x) (y))`. + //. + //. ```javascript + //. > S.concat (Identity ([1, 2, 3])) (Identity ([4, 5, 6])) + //. Identity ([1, 2, 3, 4, 5, 6]) + //. ``` + function Identity$prototype$concat(other) { + return Identity (Z.concat (this.value, other.value)); + } + + //# Identity#fantasy-land/map :: Identity a ~> (a -> b) -> Identity b + //. + //. `map (f) (Identity (x))` is equivalent to `Identity (f (x))`. + //. + //. ```javascript + //. > S.map (Math.sqrt) (Identity (64)) + //. Identity (8) + //. ``` + function Identity$prototype$map(f) { + return Identity (f (this.value)); + } + + //# Identity#fantasy-land/ap :: Identity a ~> Identity (a -> b) -> Identity b + //. + //. `ap (Identity (f)) (Identity (x))` is equivalent to `Identity (f (x))`. + //. + //. ```javascript + //. > S.ap (Identity (Math.sqrt)) (Identity (64)) + //. Identity (8) + //. ``` + function Identity$prototype$ap(other) { + return Identity (other.value (this.value)); + } + + //# Identity#fantasy-land/chain :: Identity a ~> (a -> Identity b) -> Identity b + //. + //. `chain (f) (Identity (x))` is equivalent to `f (x)`. + //. + //. ```javascript + //. > S.chain (n => Identity (n + 1)) (Identity (99)) + //. Identity (100) + //. ``` + function Identity$prototype$chain(f) { + return f (this.value); + } + + //# Identity#fantasy-land/reduce :: Identity a ~> ((b, a) -> b, b) -> b + //. + //. `reduce (f) (x) (Identity (y))` is equivalent to `f (x) (y)`. + //. + //. ```javascript + //. > S.reduce (S.concat) ([1, 2, 3]) (Identity ([4, 5, 6])) + //. [1, 2, 3, 4, 5, 6] + //. ``` + function Identity$prototype$reduce(f, x) { + return f (x, this.value); + } + + //# Identity#fantasy-land/traverse :: Applicative f => Identity a ~> (TypeRep f, a -> f b) -> f (Identity b) + //. + //. `traverse (_) (f) (Identity (x))` is equivalent to + //. `map (Identity) (f (x))`. + //. + //. ```javascript + //. > S.traverse (Array) (x => [x + 1, x + 2, x + 3]) (Identity (100)) + //. [Identity (101), Identity (102), Identity (103)] + //. ``` + function Identity$prototype$traverse(typeRep, f) { + return Z.map (Identity, f (this.value)); + } + + //# Identity#fantasy-land/extend :: Identity a ~> (Identity a -> b) -> Identity b + //. + //. `extend (f) (Identity (x))` is equivalent to + //. `Identity (f (Identity (x)))`. + //. + //. ```javascript + //. > S.extend (S.reduce (S.add) (1)) (Identity (99)) + //. Identity (100) + //. ``` + function Identity$prototype$extend(f) { + return Identity (f (this)); + } + + //# Identity#fantasy-land/extract :: Identity a ~> () -> a + //. + //. `extract (Identity (x))` is equivalent to `x`. + //. + //. ```javascript + //. > S.extract (Identity (42)) + //. 42 + //. ``` + function Identity$prototype$extract() { + return this.value; + } + + return Identity; + +})); + +//. [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..a9cb4a2 --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "sanctuary-identity", + "version": "0.0.0", + "description": "Fantasy Land -compliant Identity type", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/sanctuary-js/sanctuary-identity.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-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..5978062 --- /dev/null +++ b/test/index.js @@ -0,0 +1,359 @@ +'use strict'; + +const assert = require ('assert'); + +const laws = require ('fantasy-laws'); +const jsc = require ('jsverify'); +const show = require ('sanctuary-show'); +const Z = require ('sanctuary-type-classes'); +const type = require ('sanctuary-type-identifiers'); +const Useless = require ('sanctuary-useless'); + +const Identity = require ('..'); + + +// IdentityArb :: Arbitrary a -> Arbitrary (Identity a) +const IdentityArb = arb => arb.smap (Identity, Z.extract, show); + +// NonEmpty :: Arbitrary a -> Arbitrary (NonEmpty a) +const NonEmpty = arb => jsc.suchthat (arb, x => not (empty (x))); + +// NumberArb :: Arbitrary Number +const NumberArb = jsc.oneof ( + jsc.constant (NaN), + jsc.constant (-Infinity), + jsc.constant (Number.MIN_SAFE_INTEGER), + jsc.constant (-10000), + jsc.constant (-9999), + jsc.constant (-0.5), + jsc.constant (-0), + jsc.constant (0), + jsc.constant (0.5), + jsc.constant (9999), + jsc.constant (10000), + jsc.constant (Number.MAX_SAFE_INTEGER), + jsc.constant (Infinity) +); + +// empty :: Monoid m => m -> Boolean +const empty = m => Z.equals (m, Z.empty (m.constructor)); + +// not :: Boolean -> Boolean +const not = b => !b; + +// 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 ('Identity', () => { + + test ('metadata', () => { + eq (typeof Identity) ('function'); + eq (Identity.name) ('Identity'); + eq (Identity.length) (1); + }); + + test ('@@type', () => { + eq (type (Identity (0))) ('sanctuary-identity/Identity@1'); + eq (type.parse (type (Identity (0)))) + ({namespace: 'sanctuary-identity', name: 'Identity', version: 1}); + }); + + test ('@@show', () => { + eq (show (Identity (['foo', 'bar', 'baz']))) + ('Identity (["foo", "bar", "baz"])'); + eq (show (Identity (Identity (Identity (-0))))) + ('Identity (Identity (Identity (-0)))'); + }); + +}); + +suite ('type-class predicates', () => { + + test ('Setoid', () => { + eq (Z.Setoid.test (Identity (Useless))) (false); + eq (Z.Setoid.test (Identity (/(?:)/))) (true); + }); + + test ('Ord', () => { + eq (Z.Ord.test (Identity (Useless))) (false); + eq (Z.Ord.test (Identity (/(?:)/))) (false); + eq (Z.Ord.test (Identity (0))) (true); + }); + + test ('Semigroupoid', () => { + eq (Z.Semigroupoid.test (Identity ([]))) (false); + }); + + test ('Category', () => { + eq (Z.Category.test (Identity ([]))) (false); + }); + + test ('Semigroup', () => { + eq (Z.Semigroup.test (Identity (Useless))) (false); + eq (Z.Semigroup.test (Identity (0))) (false); + eq (Z.Semigroup.test (Identity ([]))) (true); + }); + + test ('Monoid', () => { + eq (Z.Monoid.test (Identity ([]))) (false); + }); + + test ('Group', () => { + eq (Z.Group.test (Identity ([]))) (false); + }); + + test ('Filterable', () => { + eq (Z.Filterable.test (Identity ([]))) (false); + }); + + test ('Functor', () => { + eq (Z.Functor.test (Identity (Useless))) (true); + }); + + test ('Bifunctor', () => { + eq (Z.Bifunctor.test (Identity ([]))) (false); + }); + + test ('Profunctor', () => { + eq (Z.Profunctor.test (Identity (Math.sqrt))) (false); + }); + + test ('Apply', () => { + eq (Z.Apply.test (Identity (Useless))) (true); + }); + + test ('Applicative', () => { + eq (Z.Applicative.test (Identity (Useless))) (true); + }); + + test ('Chain', () => { + eq (Z.Chain.test (Identity (Useless))) (true); + }); + + test ('ChainRec', () => { + eq (Z.ChainRec.test (Identity (Useless))) (true); + }); + + test ('Monad', () => { + eq (Z.Monad.test (Identity (Useless))) (true); + }); + + test ('Alt', () => { + eq (Z.Alt.test (Identity ([]))) (false); + }); + + test ('Plus', () => { + eq (Z.Plus.test (Identity ([]))) (false); + }); + + test ('Alternative', () => { + eq (Z.Alternative.test (Identity ([]))) (false); + }); + + test ('Foldable', () => { + eq (Z.Foldable.test (Identity (Useless))) (true); + }); + + test ('Traversable', () => { + eq (Z.Traversable.test (Identity (Useless))) (true); + }); + + test ('Extend', () => { + eq (Z.Extend.test (Identity (Useless))) (true); + }); + + test ('Comonad', () => { + eq (Z.Comonad.test (Identity (Useless))) (true); + }); + + test ('Contravariant', () => { + eq (Z.Contravariant.test (Identity (Math.sqrt))) (false); + }); + +}); + +suite ('Setoid laws', () => { + testLaws (laws.Setoid) ({ + reflexivity: [ + IdentityArb (NumberArb), + ], + symmetry: [ + IdentityArb (NumberArb), + IdentityArb (NumberArb), + ], + transitivity: [ + IdentityArb (NumberArb), + IdentityArb (NumberArb), + IdentityArb (NumberArb), + ], + }); +}); + +suite ('Ord laws', () => { + testLaws (laws.Ord) ({ + totality: [ + IdentityArb (NumberArb), + IdentityArb (NumberArb), + ], + antisymmetry: [ + IdentityArb (NumberArb), + IdentityArb (NumberArb), + IdentityArb (NumberArb), + ], + transitivity: [ + IdentityArb (NumberArb), + IdentityArb (NumberArb), + IdentityArb (NumberArb), + ], + }); +}); + +suite ('Semigroup laws', () => { + testLaws (laws.Semigroup (Z.equals)) ({ + associativity: [ + IdentityArb (jsc.string), + IdentityArb (jsc.string), + IdentityArb (jsc.string), + ], + }); +}); + +suite ('Functor laws', () => { + testLaws (laws.Functor (Z.equals)) ({ + identity: [ + IdentityArb (NumberArb), + ], + composition: [ + IdentityArb (NumberArb), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], + }); +}); + +suite ('Apply laws', () => { + testLaws (laws.Apply (Z.equals)) ({ + composition: [ + IdentityArb (jsc.constant (Math.sqrt)), + IdentityArb (jsc.constant (Math.abs)), + IdentityArb (NumberArb), + ], + }); +}); + +suite ('Applicative laws', () => { + testLaws (laws.Applicative (Z.equals, Identity)) ({ + identity: [ + IdentityArb (NumberArb), + ], + homomorphism: [ + jsc.constant (Math.abs), + NumberArb, + ], + interchange: [ + IdentityArb (jsc.constant (Math.abs)), + NumberArb, + ], + }); +}); + +suite ('Chain laws', () => { + testLaws (laws.Chain (Z.equals)) ({ + associativity: [ + IdentityArb (jsc.asciistring), + jsc.constant (s => Identity (s.replace (/[A-Z]/g, ''))), + jsc.constant (s => Identity (s.toUpperCase ())), + ], + }); +}); + +suite ('ChainRec laws', () => { + testLaws (laws.ChainRec (Z.equals, Identity)) ({ + equivalence: [ + jsc.constant (x => x >= 0), + jsc.constant (x => Identity (x + 1)), + jsc.constant (x => Identity (x * x)), + jsc.integer, + ], + }); +}); + +suite ('Monad laws', () => { + testLaws (laws.Monad (Z.equals, Identity)) ({ + leftIdentity: [ + jsc.constant (x => Identity ([x, x])), + IdentityArb (NumberArb), + ], + rightIdentity: [ + IdentityArb (NumberArb), + ], + }); +}); + +suite ('Foldable laws', () => { + testLaws (laws.Foldable (Z.equals)) ({ + associativity: [ + jsc.constant (Z.concat), + jsc.string, + IdentityArb (jsc.string), + ], + }); +}); + +suite ('Traversable laws', () => { + testLaws (laws.Traversable (Z.equals)) ({ + naturality: [ + jsc.constant (Array), + jsc.constant (Identity), + jsc.constant (xs => Identity (xs[0])), + IdentityArb (NonEmpty (jsc.array (NumberArb))), + ], + identity: [ + jsc.constant (Array), + IdentityArb (NumberArb), + ], + composition: [ + jsc.constant (Array), + jsc.constant (Identity), + IdentityArb (jsc.array (IdentityArb (NumberArb))), + ], + }); +}); + +suite ('Extend laws', () => { + testLaws (laws.Extend (Z.equals)) ({ + associativity: [ + IdentityArb (jsc.integer), + jsc.constant (identity => Z.extract (identity) + 1), + jsc.constant (identity => Math.pow (Z.extract (identity), 2)), + ], + }); +}); + +suite ('Comonad laws', () => { + testLaws (laws.Comonad (Z.equals)) ({ + leftIdentity: [ + IdentityArb (NumberArb), + ], + rightIdentity: [ + IdentityArb (NumberArb), + jsc.constant (identity => Math.pow (Z.extract (identity), 2)), + ], + }); +}); 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