From 90744d5b03e20d5ae55d9ca5f2afce48d7106a4d Mon Sep 17 00:00:00 2001 From: David Chambers Date: Wed, 7 Jun 2017 23:12:54 -0500 Subject: [PATCH] everything Co-authored-by: Gabe Johnson Co-authored-by: Matt Willhite --- .circleci/config.yml | 36 ++++ .config | 2 + .eslintrc.json | 17 ++ .gitignore | 2 + .npmrc | 1 + CONTRIBUTING.md | 27 +++ LICENSE | 24 +++ index.js | 381 +++++++++++++++++++++++++++++++++++++++++++ package.json | 37 +++++ test/.eslintrc.json | 6 + test/index.js | 350 +++++++++++++++++++++++++++++++++++++++ test/mocha.opts | 1 + 12 files changed, 884 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..8a86c7f --- /dev/null +++ b/.config @@ -0,0 +1,2 @@ +repo-owner = sanctuary-js +repo-name = sanctuary-pair diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..cde2192 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,17 @@ +{ + "root": true, + "extends": ["./node_modules/sanctuary-style/eslint-es3.json"], + "overrides": [ + { + "files": ["*.md"], + "globals": {"$": false, "Pair": false, "S": false, "Z": false, "show": false, "type": false} + }, + { + "files": ["index.js"], + "globals": {"Symbol": false}, + "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..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..c4a117a --- /dev/null +++ b/index.js @@ -0,0 +1,381 @@ + /* *\ + // \\ + // @@ @@ @@ @@ \\ + // @@ @@ @@ \\ + \\ @@ @@ @@ // + \\ @@ @@ @ @@ @ // + \\ / @ // + \* @@@@ */ + +//. Fantasy Land +//. +//. # sanctuary-pair +//. +//. Pair is the canonical product type: a value of type `Pair a b` always +//. contains exactly two values: one of type `a`; one 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.sanctuaryPair = 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 PairType = $.BinaryType + ('sanctuary-pair/Pair') + ('') + (function(x) { return type (x) === Pair['@@type']; }) + (function(p) { return [p.fst]; }) + (function(p) { return [p.snd]; }); + var env = Z.concat (S.env, + [$.TypeClass, PairType ($.Unknown) ($.Unknown)]); + return S.create ({checkTypes: true, env: env}); + } ()); + } + + var prototype = { + /* eslint-disable key-spacing */ + 'constructor': Pair, + '@@show': Pair$prototype$show, + 'fantasy-land/compose': Pair$prototype$compose, + 'fantasy-land/map': Pair$prototype$map, + 'fantasy-land/bimap': Pair$prototype$bimap, + 'fantasy-land/reduce': Pair$prototype$reduce, + 'fantasy-land/traverse': Pair$prototype$traverse, + 'fantasy-land/extend': Pair$prototype$extend, + 'fantasy-land/extract': Pair$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' + ] = Pair$prototype$show; + + /* istanbul ignore else */ + if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') { + prototype[Symbol.iterator] = function() { + return [this.fst, this.snd][Symbol.iterator] (); + }; + } + + //. `Pair 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 (Pair (Useless) (Useless)) ? '\u2705 ' : + //. . Z[k].test (Pair (['foo']) (['bar'])) ? '\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 ✅ * ', // if ‘a’ satisfies Semigroup + //. . 'Applicative ❌ ', + //. . 'Chain ✅ * ', // if ‘a’ satisfies Semigroup + //. . 'ChainRec ❌ ', + //. . 'Monad ❌ ', + //. . 'Alt ❌ ', + //. . 'Plus ❌ ', + //. . 'Alternative ❌ ', + //. . 'Foldable ✅ ', + //. . 'Traversable ✅ ', + //. . 'Extend ✅ ', + //. . 'Comonad ✅ ', + //. . 'Contravariant ❌ ' ] + //. ``` + + //# Pair :: a -> b -> Pair a b + //. + //. Pair's sole data constructor. Additionally, it serves as the + //. Pair [type representative][]. + //. + //. ```javascript + //. > Pair (1) (2) + //. Pair (1) (2) + //. ``` + function Pair(fst) { + return function(snd) { + var pair = Object.create (prototype); + if (Z.Setoid.test (fst) && Z.Setoid.test (snd)) { + pair['fantasy-land/equals'] = Pair$prototype$equals; + if (Z.Ord.test (fst) && Z.Ord.test (snd)) { + pair['fantasy-land/lte'] = Pair$prototype$lte; + } + } + if (Z.Semigroup.test (fst)) { + if (Z.Semigroup.test (snd)) { + pair['fantasy-land/concat'] = Pair$prototype$concat; + } + pair['fantasy-land/ap'] = Pair$prototype$ap; + pair['fantasy-land/chain'] = Pair$prototype$chain; + } + pair.fst = fst; + pair.snd = snd; + return pair; + }; + } + + //# Pair.fst :: Pair a b -> a + //. + //. `fst (Pair (x) (y))` is equivalent to `x`. + //. + //. ```javascript + //. > Pair.fst (Pair ('abc') ([1, 2, 3])) + //. 'abc' + //. ``` + Pair.fst = function(p) { return p.fst; }; + + //# Pair.snd :: Pair a b -> b + //. + //. `snd (Pair (x) (y))` is equivalent to `y`. + //. + //. ```javascript + //. > Pair.snd (Pair ('abc') ([1, 2, 3])) + //. [1, 2, 3] + //. ``` + Pair.snd = function(p) { return p.snd; }; + + //# Pair.swap :: Pair a b -> Pair b a + //. + //. `swap (Pair (x) (y))` is equivalent to `Pair (y) (x)`. + //. + //. ```javascript + //. > Pair.swap (Pair ('abc') ([1, 2, 3])) + //. Pair ([1, 2, 3]) ('abc') + //. ``` + Pair.swap = function(p) { return Pair (p.snd) (p.fst); }; + + //# Pair.@@type :: String + //. + //. Pair [type identifier][]. + //. + //. ```javascript + //. > type (Pair ('abc') ([1, 2, 3])) + //. 'sanctuary-pair/Pair@1' + //. + //. > type.parse (type (Pair ('abc') ([1, 2, 3]))) + //. {namespace: 'sanctuary-pair', name: 'Pair', version: 1} + //. ``` + Pair['@@type'] = 'sanctuary-pair/Pair@1'; + + //# Pair#@@show :: (Showable a, Showable b) => Pair a b ~> () -> String + //. + //. `show (Pair (x) (y))` is equivalent to + //. `'Pair (' + show (x) + ') (' + show (y) + ')'`. + //. + //. ```javascript + //. > show (Pair ('abc') ([1, 2, 3])) + //. 'Pair ("abc") ([1, 2, 3])' + //. ``` + function Pair$prototype$show() { + return 'Pair (' + show (this.fst) + ') (' + show (this.snd) + ')'; + } + + //# Pair#fantasy-land/equals :: (Setoid a, Setoid b) => Pair a b ~> Pair a b -> Boolean + //. + //. `Pair (x) (y)` is equal to `Pair (v) (w)` [iff][] `x` is equal to `v` + //. and `y` is equal to `w` according to [`Z.equals`][]. + //. + //. ```javascript + //. > S.equals (Pair ('abc') ([1, 2, 3])) (Pair ('abc') ([1, 2, 3])) + //. true + //. + //. > S.equals (Pair ('abc') ([1, 2, 3])) (Pair ('abc') ([3, 2, 1])) + //. false + //. ``` + function Pair$prototype$equals(other) { + return Z.equals (this.fst, other.fst) && Z.equals (this.snd, other.snd); + } + + //# Pair#fantasy-land/lte :: (Ord a, Ord b) => Pair a b ~> Pair a b -> Boolean + //. + //. `Pair (x) (y)` is less than or equal to `Pair (v) (w)` [iff][] `x` is + //. less than `v` or `x` is equal to `v` and `y` is less than or equal to + //. `w` according to [`Z.lte`][]. + //. + //. ```javascript + //. > S.filter (S.lte (Pair ('b') (2))) + //. . ([Pair ('a') (1), Pair ('a') (2), Pair ('a') (3), + //. . Pair ('b') (1), Pair ('b') (2), Pair ('b') (3), + //. . Pair ('c') (1), Pair ('c') (2), Pair ('c') (3)]) + //. [ Pair ('a') (1), + //. . Pair ('a') (2), + //. . Pair ('a') (3), + //. . Pair ('b') (1), + //. . Pair ('b') (2) ] + //. ``` + function Pair$prototype$lte(other) { + return Z.equals (this.fst, other.fst) ? Z.lte (this.snd, other.snd) + : Z.lte (this.fst, other.fst); + } + + //# Pair#fantasy-land/compose :: Pair a b ~> Pair b c -> Pair a c + //. + //. `compose (Pair (x) (y)) (Pair (v) (w))` is equivalent to `Pair (v) (y)`. + //. + //. ```javascript + //. > S.compose (Pair ('a') (0)) (Pair ([1, 2, 3]) ('b')) + //. Pair ([1, 2, 3]) (0) + //. ``` + function Pair$prototype$compose(other) { + return Pair (this.fst) (other.snd); + } + + //# Pair#fantasy-land/concat :: (Semigroup a, Semigroup b) => Pair a b ~> Pair a b -> Pair a b + //. + //. `concat (Pair (x) (y)) (Pair (v) (w))` is equivalent to + //. `Pair (concat (x) (v)) (concat (y) (w))`. + //. + //. ```javascript + //. > S.concat (Pair ('abc') ([1, 2, 3])) (Pair ('xyz') ([4, 5, 6])) + //. Pair ('abcxyz') ([1, 2, 3, 4, 5, 6]) + //. ``` + function Pair$prototype$concat(other) { + return Pair (Z.concat (this.fst, other.fst)) + (Z.concat (this.snd, other.snd)); + } + + //# Pair#fantasy-land/map :: Pair a b ~> (b -> c) -> Pair a c + //. + //. `map (f) (Pair (x) (y))` is equivalent to `Pair (x) (f (y))`. + //. + //. ```javascript + //. > S.map (Math.sqrt) (Pair ('abc') (256)) + //. Pair ('abc') (16) + //. ``` + function Pair$prototype$map(f) { + return Pair (this.fst) (f (this.snd)); + } + + //# Pair#fantasy-land/bimap :: Pair a c ~> (a -> b, c -> d) -> Pair b d + //. + //. `bimap (f) (g) (Pair (x) (y))` is equivalent to `Pair (f (x)) (g (y))`. + //. + //. ```javascript + //. > S.bimap (S.toUpper) (Math.sqrt) (Pair ('abc') (256)) + //. Pair ('ABC') (16) + //. ``` + function Pair$prototype$bimap(f, g) { + return Pair (f (this.fst)) (g (this.snd)); + } + + //# Pair#fantasy-land/ap :: Semigroup a => Pair a b ~> Pair a (b -> c) -> Pair a c + //. + //. `ap (Pair (v) (f)) (Pair (x) (y))` is equivalent to + //. `Pair (concat (v) (x)) (f (y))`. + //. + //. ```javascript + //. > S.ap (Pair ('abc') (Math.sqrt)) (Pair ('xyz') (256)) + //. Pair ('abcxyz') (16) + //. ``` + function Pair$prototype$ap(other) { + return Pair (Z.concat (other.fst, this.fst)) (other.snd (this.snd)); + } + + //# Pair#fantasy-land/chain :: Semigroup a => Pair a b ~> (b -> Pair a c) -> Pair a c + //. + //. `chain (f) (Pair (x) (y))` is equivalent to + //. `Pair (concat (x) (fst (f (y)))) (snd (f (y)))`. + //. + //. ```javascript + //. > S.chain (n => Pair (show (n)) (Math.sqrt (n))) (Pair ('abc') (256)) + //. Pair ('abc256') (16) + //. ``` + function Pair$prototype$chain(f) { + var other = f (this.snd); + return Pair (Z.concat (this.fst, other.fst)) (other.snd); + } + + //# Pair#fantasy-land/reduce :: Pair a b ~> ((c, b) -> c, c) -> c + //. + //. `reduce (f) (x) (Pair (v) (w))` is equivalent to `f (x) (w)`. + //. + //. ```javascript + //. > S.reduce (S.concat) ([1, 2, 3]) (Pair ('abc') ([4, 5, 6])) + //. [1, 2, 3, 4, 5, 6] + //. ``` + function Pair$prototype$reduce(f, x) { + return f (x, this.snd); + } + + //# Pair#fantasy-land/traverse :: Applicative f => Pair a b ~> (TypeRep f, b -> f c) -> f (Pair a c) + //. + //. `traverse (_) (f) (Pair (x) (y))` is equivalent to + //. `map (Pair (x)) (f (y))`. + //. + //. ```javascript + //. > S.traverse (Array) (S.words) (Pair (123) ('foo bar baz')) + //. [Pair (123) ('foo'), Pair (123) ('bar'), Pair (123) ('baz')] + //. ``` + function Pair$prototype$traverse(typeRep, f) { + return Z.map (Pair (this.fst), f (this.snd)); + } + + //# Pair#fantasy-land/extend :: Pair a b ~> (Pair a b -> c) -> Pair a c + //. + //. `extend (f) (Pair (x) (y))` is equivalent to + //. `Pair (x) (f (Pair (x) (y)))`. + //. + //. ```javascript + //. > S.extend (S.reduce (S.add) (1)) (Pair ('abc') (99)) + //. Pair ('abc') (100) + //. ``` + function Pair$prototype$extend(f) { + return Pair (this.fst) (f (this)); + } + + //# Pair#fantasy-land/extract :: Pair a b ~> () -> b + //. + //. `extract (Pair (x) (y))` is equivalent to `y`. + //. + //. ```javascript + //. > S.extract (Pair ('abc') ([1, 2, 3])) + //. [1, 2, 3] + //. ``` + function Pair$prototype$extract() { + return this.snd; + } + + return Pair; + +})); + +//. [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..b2a7eeb --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "sanctuary-pair", + "version": "0.0.0", + "description": "Fantasy Land -compliant Pair type", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/sanctuary-js/sanctuary-pair.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..cb8e962 --- /dev/null +++ b/test/index.js @@ -0,0 +1,350 @@ +'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 Pair = require ('..'); + + +// IdentityArb :: Arbitrary a -> Arbitrary (Identity a) +const IdentityArb = arb => arb.smap (Identity, Z.extract, show); + +// PairArb :: Arbitrary a -> Arbitrary b -> Arbitrary (Pair a b) +const PairArb = arbL => arbR => (jsc.pair (arbL, arbR)).smap ( + ([fst, snd]) => Pair (fst) (snd), + p => [p.fst, p.snd], + show +); + +// testLaws :: Object -> Object -> Undefined +const testLaws = laws => arbs => { + (Object.keys (laws)).forEach (name => { + eq (laws[name].length) (arbs[name].length); + 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 ('Pair', () => { + + test ('metadata', () => { + eq (typeof Pair) ('function'); + eq (Pair.name) ('Pair'); + eq (Pair.length) (1); + }); + + test ('@@type', () => { + eq (type (Pair (0) (0))) ('sanctuary-pair/Pair@1'); + eq (type.parse (type (Pair (0) (0)))) + ({namespace: 'sanctuary-pair', name: 'Pair', version: 1}); + }); + + test ('@@show', () => { + eq (show (Pair (['foo', 'bar', 'baz']) (-0))) + ('Pair (["foo", "bar", "baz"]) (-0)'); + }); + + test ('@@iterator', () => { + const pair = Pair ('foo') ('bar'); + const iterator = pair[Symbol.iterator] (); + eq (iterator.next ()) ({value: 'foo', done: false}); + eq (iterator.next ()) ({value: 'bar', done: false}); + eq (iterator.next ()) ({value: undefined, done: true}); + }); + + test ('Pair.fst', () => { + eq (Pair.fst (Pair ('foo') (42))) ('foo'); + }); + + test ('Pair.snd', () => { + eq (Pair.snd (Pair ('foo') (42))) (42); + }); + + test ('Pair.swap', () => { + eq (Pair.swap (Pair ('foo') (42))) (Pair (42) ('foo')); + }); + +}); + +suite ('type-class predicates', () => { + + test ('Setoid', () => { + eq (Z.Setoid.test (Pair (Useless) (Useless))) (false); + eq (Z.Setoid.test (Pair (Useless) ('world'))) (false); + eq (Z.Setoid.test (Pair ('hello') (Useless))) (false); + eq (Z.Setoid.test (Pair ('hello') ('world'))) (true); + }); + + test ('Ord', () => { + eq (Z.Ord.test (Pair (Useless) (Useless))) (false); + eq (Z.Ord.test (Pair (Useless) ('world'))) (false); + eq (Z.Ord.test (Pair ('hello') (Useless))) (false); + eq (Z.Ord.test (Pair ('hello') ('world'))) (true); + }); + + test ('Semigroupoid', () => { + eq (Z.Semigroupoid.test (Pair (Useless) (Useless))) (true); + }); + + test ('Category', () => { + eq (Z.Category.test (Pair (Math.sqrt) (Math.sqrt))) (false); + }); + + test ('Semigroup', () => { + eq (Z.Semigroup.test (Pair (Useless) (Useless))) (false); + eq (Z.Semigroup.test (Pair (Useless) ('world'))) (false); + eq (Z.Semigroup.test (Pair ('hello') (Useless))) (false); + eq (Z.Semigroup.test (Pair ('hello') ('world'))) (true); + }); + + test ('Monoid', () => { + eq (Z.Monoid.test (Pair ('hello') ('world'))) (false); + }); + + test ('Functor', () => { + eq (Z.Functor.test (Pair (Useless) (Useless))) (true); + }); + + test ('Bifunctor', () => { + eq (Z.Bifunctor.test (Pair (Useless) (Useless))) (true); + }); + + test ('Profunctor', () => { + eq (Z.Profunctor.test (Pair (Math.sqrt) (Math.sqrt))) (false); + }); + + test ('Apply', () => { + eq (Z.Apply.test (Pair (Useless) (Useless))) (false); + eq (Z.Apply.test (Pair (Useless) ('world'))) (false); + eq (Z.Apply.test (Pair ('hello') (Useless))) (true); + eq (Z.Apply.test (Pair ('hello') ('world'))) (true); + }); + + test ('Applicative', () => { + eq (Z.Applicative.test (Pair ([]) ([]))) (false); + }); + + test ('Chain', () => { + eq (Z.Chain.test (Pair (Useless) (Useless))) (false); + eq (Z.Chain.test (Pair (Useless) ('world'))) (false); + eq (Z.Chain.test (Pair ('hello') (Useless))) (true); + eq (Z.Chain.test (Pair ('hello') ('world'))) (true); + }); + + test ('ChainRec', () => { + eq (Z.ChainRec.test (Pair (Useless) (Useless))) (false); + eq (Z.ChainRec.test (Pair (Useless) ('world'))) (false); + eq (Z.ChainRec.test (Pair ('hello') (Useless))) (false); + eq (Z.ChainRec.test (Pair ('hello') ('world'))) (false); + }); + + test ('Monad', () => { + eq (Z.Monad.test (Pair ([]) ([]))) (false); + }); + + test ('Alt', () => { + eq (Z.Alt.test (Pair ([]) ([]))) (false); + }); + + test ('Plus', () => { + eq (Z.Plus.test (Pair ([]) ([]))) (false); + }); + + test ('Alternative', () => { + eq (Z.Alternative.test (Pair ([]) ([]))) (false); + }); + + test ('Foldable', () => { + eq (Z.Foldable.test (Pair (Useless) (Useless))) (true); + }); + + test ('Traversable', () => { + eq (Z.Traversable.test (Pair (Useless) (Useless))) (true); + }); + + test ('Extend', () => { + eq (Z.Extend.test (Pair (Useless) (Useless))) (true); + }); + + test ('Comonad', () => { + eq (Z.Comonad.test (Pair (Useless) (Useless))) (true); + }); + + test ('Contravariant', () => { + eq (Z.Contravariant.test (Pair (Math.sqrt) (Math.sqrt))) (false); + }); + +}); + +suite ('Setoid laws', () => { + testLaws (laws.Setoid) ({ + reflexivity: [ + PairArb (jsc.string) (jsc.falsy), + ], + symmetry: [ + PairArb (jsc.bool) (jsc.bool), + PairArb (jsc.bool) (jsc.bool), + ], + transitivity: [ + PairArb (jsc.bool) (jsc.bool), + PairArb (jsc.bool) (jsc.bool), + PairArb (jsc.bool) (jsc.bool), + ], + }); +}); + +suite ('Ord laws', () => { + testLaws (laws.Ord) ({ + totality: [ + PairArb (jsc.string) (jsc.number), + PairArb (jsc.string) (jsc.number), + ], + antisymmetry: [ + PairArb (jsc.string) (jsc.number), + PairArb (jsc.string) (jsc.number), + ], + transitivity: [ + PairArb (jsc.string) (jsc.number), + PairArb (jsc.string) (jsc.number), + PairArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Semigroup laws', () => { + testLaws (laws.Semigroup (Z.equals)) ({ + associativity: [ + PairArb (jsc.string) (jsc.string), + PairArb (jsc.string) (jsc.string), + PairArb (jsc.string) (jsc.string), + ], + }); +}); + +suite ('Semigroupoid laws', () => { + testLaws (laws.Semigroupoid (Z.equals)) ({ + associativity: [ + PairArb (jsc.string) (jsc.string), + PairArb (jsc.string) (jsc.string), + PairArb (jsc.string) (jsc.string), + ], + }); +}); + +suite ('Functor laws', () => { + testLaws (laws.Functor (Z.equals)) ({ + identity: [ + PairArb (jsc.string) (jsc.number), + ], + composition: [ + PairArb (jsc.string) (jsc.number), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], + }); +}); + +suite ('Bifunctor laws', () => { + testLaws (laws.Bifunctor (Z.equals)) ({ + identity: [ + PairArb (jsc.string) (jsc.number), + ], + composition: [ + PairArb (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: [ + PairArb (jsc.string) (jsc.constant (Math.sqrt)), + PairArb (jsc.string) (jsc.constant (Math.abs)), + PairArb (jsc.string) (jsc.number), + ], + }); +}); + +suite ('Chain laws', () => { + testLaws (laws.Chain (Z.equals)) ({ + associativity: [ + PairArb (jsc.string) (jsc.number), + jsc.constant (n => Pair (show (n)) (n + 1)), + jsc.constant (n => Pair (show (n)) (n * n)), + ], + }); +}); + +suite ('Foldable laws', () => { + testLaws (laws.Foldable (Z.equals)) ({ + associativity: [ + jsc.constant (Z.concat), + jsc.string, + PairArb (jsc.number) (jsc.string), + ], + }); +}); + +suite ('Traversable laws', () => { + testLaws (laws.Traversable (Z.equals)) ({ + naturality: [ + jsc.constant (Identity), + jsc.constant (Array), + jsc.constant (identity => [Z.extract (identity)]), + PairArb (jsc.string) (IdentityArb (jsc.number)), + ], + identity: [ + jsc.constant (Identity), + PairArb (jsc.string) (jsc.number), + ], + composition: [ + jsc.constant (Identity), + jsc.constant (Pair), + PairArb (jsc.string) (IdentityArb (PairArb (jsc.string) (jsc.number))), + ], + }); +}); + +suite ('Extend laws', () => { + testLaws (laws.Extend (Z.equals)) ({ + associativity: [ + PairArb (jsc.string) (jsc.integer), + jsc.constant (pair => Z.extract (pair) + 1), + jsc.constant (pair => Math.pow (Z.extract (pair), 2)), + ], + }); +}); + +suite ('Comonad laws', () => { + testLaws (laws.Comonad (Z.equals)) ({ + leftIdentity: [ + PairArb (jsc.string) (jsc.integer), + ], + rightIdentity: [ + PairArb (jsc.string) (jsc.integer), + jsc.constant (pair => Math.pow (Z.extract (pair), 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