From 64cc690cc5ced75875c151df78816f1bb4a7f894 Mon Sep 17 00:00:00 2001 From: David Chambers Date: Thu, 23 Feb 2017 20:49:42 +0000 Subject: [PATCH] everything Co-authored-by: Stefano Vozza --- .circleci/config.yml | 36 +++ .config | 2 + .eslintrc.json | 16 ++ .gitignore | 2 + .npmrc | 1 + CONTRIBUTING.md | 27 +++ LICENSE | 25 ++ index.js | 531 +++++++++++++++++++++++++++++++++++++++++++ package.json | 38 ++++ test/.eslintrc.json | 6 + test/index.js | 449 ++++++++++++++++++++++++++++++++++++ test/mocha.opts | 1 + 12 files changed, 1134 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..c60b8a7 --- /dev/null +++ b/.config @@ -0,0 +1,2 @@ +repo-owner = sanctuary-js +repo-name = sanctuary-maybe diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..b80cc06 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "root": true, + "extends": ["./node_modules/sanctuary-style/eslint-es3.json"], + "overrides": [ + { + "files": ["*.md"], + "globals": {"$": false, "Just": false, "Maybe": false, "Nothing": 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..13ad2b4 --- /dev/null +++ b/index.js @@ -0,0 +1,531 @@ +/* + ,______ ______, ,________,,_____,,_____,,__________ ,__________, + | \/ | | || || || \ | | + |_, ,_| |_ _||_ || _||_, __ ||_, _____| + | \ / | / \ \ \/ / | / | | + ,_| || |_,,_/ /\ \_, \ / ,_| __ \ ,_| ___|_, + | || || || | | | | || | + |______||______||_____||_____| |____| |__________/ |__________| + */ + +//. Fantasy Land +//. +//. # sanctuary-maybe +//. +//. The Maybe type represents optional values: a value of type `Maybe a` is +//. either Nothing (the empty value) or a Just whose value is 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.sanctuaryMaybe = 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 MaybeType = $.UnaryType + ('sanctuary-maybe/Maybe') + ('') + (function(x) { return type (x) === Maybe['@@type']; }) + (function(m) { return m.isJust ? [m.value] : []; }); + var env = Z.concat (S.env, [$.TypeClass, MaybeType ($.Unknown)]); + return S.create ({checkTypes: true, env: env}); + } ()); + } + + var Maybe = {}; + + var Nothing$prototype = { + /* eslint-disable key-spacing */ + 'constructor': Maybe, + 'isNothing': true, + 'isJust': false, + '@@show': Nothing$prototype$show, + 'fantasy-land/equals': Nothing$prototype$equals, + 'fantasy-land/lte': Nothing$prototype$lte, + 'fantasy-land/concat': Nothing$prototype$concat, + 'fantasy-land/filter': Nothing$prototype$filter, + 'fantasy-land/map': Nothing$prototype$map, + 'fantasy-land/ap': Nothing$prototype$ap, + 'fantasy-land/chain': Nothing$prototype$chain, + 'fantasy-land/alt': Nothing$prototype$alt, + 'fantasy-land/reduce': Nothing$prototype$reduce, + 'fantasy-land/traverse': Nothing$prototype$traverse, + 'fantasy-land/extend': Nothing$prototype$extend + /* eslint-enable key-spacing */ + }; + + var Just$prototype = { + /* eslint-disable key-spacing */ + 'constructor': Maybe, + 'isNothing': false, + 'isJust': true, + '@@show': Just$prototype$show, + 'fantasy-land/filter': Just$prototype$filter, + 'fantasy-land/map': Just$prototype$map, + 'fantasy-land/ap': Just$prototype$ap, + 'fantasy-land/chain': Just$prototype$chain, + 'fantasy-land/alt': Just$prototype$alt, + 'fantasy-land/reduce': Just$prototype$reduce, + 'fantasy-land/traverse': Just$prototype$traverse, + 'fantasy-land/extend': Just$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'; + Nothing$prototype[inspect] = Nothing$prototype$show; + Just$prototype[inspect] = Just$prototype$show; + + //. `Maybe a` satisfies the following [Fantasy Land][] specifications: + //. + //. ```javascript + //. > const Useless = require ('sanctuary-useless') + //. + //. > S.map (k => k + ' '.repeat (16 - k.length) + + //. . (Z[k].test (Just (Useless)) ? '\u2705 ' : + //. . Z[k].test (Nothing) ? '\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 ✅ * ', // if ‘a’ satisfies Semigroup + //. . 'Group ❌ ', + //. . 'Filterable ✅ ', + //. . 'Functor ✅ ', + //. . 'Bifunctor ❌ ', + //. . 'Profunctor ❌ ', + //. . 'Apply ✅ ', + //. . 'Applicative ✅ ', + //. . 'Chain ✅ ', + //. . 'ChainRec ✅ ', + //. . 'Monad ✅ ', + //. . 'Alt ✅ ', + //. . 'Plus ✅ ', + //. . 'Alternative ✅ ', + //. . 'Foldable ✅ ', + //. . 'Traversable ✅ ', + //. . 'Extend ✅ ', + //. . 'Comonad ❌ ', + //. . 'Contravariant ❌ ' ] + //. ``` + + //# Maybe :: TypeRep Maybe + //. + //. Maybe [type representative][]. + + //# Maybe.Nothing :: Maybe a + //. + //. The empty value of type `Maybe a`. + //. + //. ```javascript + //. > Nothing + //. Nothing + //. ``` + var Nothing = Maybe.Nothing = Object.create (Nothing$prototype); + + //# Maybe.Just :: a -> Maybe a + //. + //. Constructs a value of type `Maybe a` from a value of type `a`. + //. + //. ```javascript + //. > Just (42) + //. Just (42) + //. ``` + var Just = Maybe.Just = function(value) { + var just = Object.create (Just$prototype); + if (Z.Setoid.test (value)) { + just['fantasy-land/equals'] = Just$prototype$equals; + if (Z.Ord.test (value)) { + just['fantasy-land/lte'] = Just$prototype$lte; + } + } + if (Z.Semigroup.test (value)) { + just['fantasy-land/concat'] = Just$prototype$concat; + } + just.value = value; + return just; + }; + + //# Maybe.@@type :: String + //. + //. Maybe [type identifier][]. + //. + //. ```javascript + //. > type (Just (42)) + //. 'sanctuary-maybe/Maybe@1' + //. + //. > type.parse (type (Just (42))) + //. {namespace: 'sanctuary-maybe', name: 'Maybe', version: 1} + //. ``` + Maybe['@@type'] = 'sanctuary-maybe/Maybe@1'; + + //# Maybe.fantasy-land/empty :: () -> Maybe a + //. + //. - `empty (Maybe)` is equivalent to `Nothing` + //. + //. ```javascript + //. > S.empty (Maybe) + //. Nothing + //. ``` + Maybe['fantasy-land/empty'] = function() { return Nothing; }; + + //# Maybe.fantasy-land/of :: a -> Maybe a + //. + //. - `of (Maybe) (x)` is equivalent to `Just (x)` + //. + //. ```javascript + //. > S.of (Maybe) (42) + //. Just (42) + //. ``` + Maybe['fantasy-land/of'] = Just; + + function next(x) { return {tag: next, value: x}; } + function done(x) { return {tag: done, value: x}; } + + //# Maybe.fantasy-land/chainRec :: ((a -> c, b -> c, a) -> Maybe c, a) -> Maybe b + //. + //. ```javascript + //. > Z.chainRec ( + //. . Maybe, + //. . (next, done, x) => + //. . x <= 1 ? Nothing : Just (x >= 1000 ? done (x) : next (x * x)), + //. . 1 + //. . ) + //. Nothing + //. + //. > Z.chainRec ( + //. . Maybe, + //. . (next, done, x) => + //. . x <= 1 ? Nothing : Just (x >= 1000 ? done (x) : next (x * x)), + //. . 2 + //. . ) + //. Just (65536) + //. ``` + Maybe['fantasy-land/chainRec'] = function(f, x) { + var r = next (x); + while (r.tag === next) { + var maybe = f (next, done, r.value); + if (maybe.isNothing) return maybe; + r = maybe.value; + } + return Just (r.value); + }; + + //# Maybe.fantasy-land/zero :: () -> Maybe a + //. + //. - `zero (Maybe)` is equivalent to `Nothing` + //. + //. ```javascript + //. > S.zero (Maybe) + //. Nothing + //. ``` + Maybe['fantasy-land/zero'] = function() { return Nothing; }; + + //# Maybe#@@show :: Showable a => Maybe a ~> () -> String + //. + //. - `show (Nothing)` is equivalent to `'Nothing'` + //. - `show (Just (x))` is equivalent to `'Just (' + show (x) + ')'` + //. + //. ```javascript + //. > show (Nothing) + //. 'Nothing' + //. + //. > show (Just (['foo', 'bar', 'baz'])) + //. 'Just (["foo", "bar", "baz"])' + //. ``` + function Nothing$prototype$show() { + return 'Nothing'; + } + function Just$prototype$show() { + return 'Just (' + show (this.value) + ')'; + } + + //# Maybe#fantasy-land/equals :: Setoid a => Maybe a ~> Maybe a -> Boolean + //. + //. - `Nothing` is equal to `Nothing` + //. - `Just (x)` is equal to `Just (y)` [iff][] `x` is equal to `y` + //. according to [`Z.equals`][] + //. - `Nothing` is never equal to `Just (x)` + //. + //. ```javascript + //. > S.equals (Nothing) (Nothing) + //. true + //. + //. > S.equals (Just ([1, 2, 3])) (Just ([1, 2, 3])) + //. true + //. + //. > S.equals (Just ([1, 2, 3])) (Just ([3, 2, 1])) + //. false + //. + //. > S.equals (Just ([1, 2, 3])) (Nothing) + //. false + //. ``` + function Nothing$prototype$equals(other) { + return other.isNothing; + } + function Just$prototype$equals(other) { + return other.isJust && Z.equals (this.value, other.value); + } + + //# Maybe#fantasy-land/lte :: Ord a => Maybe a ~> Maybe a -> Boolean + //. + //. - `Nothing` is (less than or) equal to `Nothing` + //. - `Just (x)` is less than or equal to `Just (y)` [iff][] `x` is less + //. than or equal to `y` according to [`Z.lte`][] + //. - `Nothing` is always less than `Just (x)` + //. + //. ```javascript + //. > S.filter (S.lte (Nothing)) ([Nothing, Just (0), Just (1), Just (2)]) + //. [Nothing] + //. + //. > S.filter (S.lte (Just (1))) ([Nothing, Just (0), Just (1), Just (2)]) + //. [Nothing, Just (0), Just (1)] + //. ``` + function Nothing$prototype$lte(other) { + return true; + } + function Just$prototype$lte(other) { + return other.isJust && Z.lte (this.value, other.value); + } + + //# Maybe#fantasy-land/concat :: Semigroup a => Maybe a ~> Maybe a -> Maybe a + //. + //. - `concat (Nothing) (Nothing)` is equivalent to `Nothing` + //. - `concat (Just (x)) (Just (y))` is equivalent to + //. `Just (concat (x) (y))` + //. - `concat (Nothing) (Just (x))` is equivalent to `Just (x)` + //. - `concat (Just (x)) (Nothing)` is equivalent to `Just (x)` + //. + //. ```javascript + //. > S.concat (Nothing) (Nothing) + //. Nothing + //. + //. > S.concat (Just ([1, 2, 3])) (Just ([4, 5, 6])) + //. Just ([1, 2, 3, 4, 5, 6]) + //. + //. > S.concat (Nothing) (Just ([1, 2, 3])) + //. Just ([1, 2, 3]) + //. + //. > S.concat (Just ([1, 2, 3])) (Nothing) + //. Just ([1, 2, 3]) + //. ``` + function Nothing$prototype$concat(other) { + return other; + } + function Just$prototype$concat(other) { + return other.isJust ? Just (Z.concat (this.value, other.value)) : this; + } + + //# Maybe#fantasy-land/filter :: Maybe a ~> (a -> Boolean) -> Maybe a + //. + //. - `filterM (p) (Nothing)` is equivalent to `Nothing` + //. - `filterM (p) (Just (x))` is equivalent to + //. `p (x) ? Just (x) : Nothing` + //. + //. ```javascript + //. > S.filterM (isFinite) (Nothing) + //. Nothing + //. + //. > S.filterM (isFinite) (Just (Infinity)) + //. Nothing + //. + //. > S.filterM (isFinite) (Just (Number.MAX_SAFE_INTEGER)) + //. Just (9007199254740991) + //. ``` + function Nothing$prototype$filter(pred) { + return this; + } + function Just$prototype$filter(pred) { + return pred (this.value) ? this : Nothing; + } + + //# Maybe#fantasy-land/map :: Maybe a ~> (a -> b) -> Maybe b + //. + //. - `map (f) (Nothing)` is equivalent to `Nothing` + //. - `map (f) (Just (x))` is equivalent to `Just (f (x))` + //. + //. ```javascript + //. > S.map (Math.sqrt) (Nothing) + //. Nothing + //. + //. > S.map (Math.sqrt) (Just (9)) + //. Just (3) + //. ``` + function Nothing$prototype$map(f) { + return this; + } + function Just$prototype$map(f) { + return Just (f (this.value)); + } + + //# Maybe#fantasy-land/ap :: Maybe a ~> Maybe (a -> b) -> Maybe b + //. + //. - `ap (Nothing) (Nothing)` is equivalent to `Nothing` + //. - `ap (Nothing) (Just (x))` is equivalent to `Nothing` + //. - `ap (Just (f)) (Nothing)` is equivalent to `Nothing` + //. - `ap (Just (f)) (Just (x))` is equivalent to `Just (f (x))` + //. + //. ```javascript + //. > S.ap (Nothing) (Nothing) + //. Nothing + //. + //. > S.ap (Nothing) (Just (9)) + //. Nothing + //. + //. > S.ap (Just (Math.sqrt)) (Nothing) + //. Nothing + //. + //. > S.ap (Just (Math.sqrt)) (Just (9)) + //. Just (3) + //. ``` + function Nothing$prototype$ap(other) { + return this; + } + function Just$prototype$ap(other) { + return other.isJust ? Just (other.value (this.value)) : other; + } + + //# Maybe#fantasy-land/chain :: Maybe a ~> (a -> Maybe b) -> Maybe b + //. + //. - `chain (f) (Nothing)` is equivalent to `Nothing` + //. - `chain (f) (Just (x))` is equivalent to `f (x)` + //. + //. ```javascript + //. > const head = xs => xs.length === 0 ? Nothing : Just (xs[0]) + //. + //. > S.chain (head) (Nothing) + //. Nothing + //. + //. > S.chain (head) (Just ([])) + //. Nothing + //. + //. > S.chain (head) (Just (['foo', 'bar', 'baz'])) + //. Just ('foo') + //. ``` + function Nothing$prototype$chain(f) { + return this; + } + function Just$prototype$chain(f) { + return f (this.value); + } + + //# Maybe#fantasy-land/alt :: Maybe a ~> Maybe a -> Maybe a + //. + //. - `alt (Nothing) (Nothing)` is equivalent to `Nothing` + //. - `alt (Nothing) (Just (x))` is equivalent to `Just (x)` + //. - `alt (Just (x)) (Nothing)` is equivalent to `Just (x)` + //. - `alt (Just (x)) (Just (y))` is equivalent to `Just (x)` + //. + //. ```javascript + //. > S.alt (Nothing) (Nothing) + //. Nothing + //. + //. > S.alt (Nothing) (Just (1)) + //. Just (1) + //. + //. > S.alt (Just (2)) (Nothing) + //. Just (2) + //. + //. > S.alt (Just (3)) (Just (4)) + //. Just (3) + //. ``` + function Nothing$prototype$alt(other) { + return other; + } + function Just$prototype$alt(other) { + return this; + } + + //# Maybe#fantasy-land/reduce :: Maybe a ~> ((b, a) -> b, b) -> b + //. + //. - `reduce (f) (x) (Nothing)` is equivalent to `x` + //. - `reduce (f) (x) (Just (y))` is equivalent to `f (x) (y)` + //. + //. ```javascript + //. > S.reduce (S.concat) ('abc') (Nothing) + //. 'abc' + //. + //. > S.reduce (S.concat) ('abc') (Just ('xyz')) + //. 'abcxyz' + //. ``` + function Nothing$prototype$reduce(f, x) { + return x; + } + function Just$prototype$reduce(f, x) { + return f (x, this.value); + } + + //# Maybe#fantasy-land/traverse :: Applicative f => Maybe a ~> (TypeRep f, a -> f b) -> f (Maybe b) + //. + //. - `traverse (A) (f) (Nothing)` is equivalent to `of (A) (Nothing)` + //. - `traverse (A) (f) (Just (x))` is equivalent to `map (Just) (f (x))` + //. + //. ```javascript + //. > S.traverse (Array) (S.words) (Nothing) + //. [Nothing] + //. + //. > S.traverse (Array) (S.words) (Just ('foo bar baz')) + //. [Just ('foo'), Just ('bar'), Just ('baz')] + //. ``` + function Nothing$prototype$traverse(typeRep, f) { + return Z.of (typeRep, this); + } + function Just$prototype$traverse(typeRep, f) { + return Z.map (Just, f (this.value)); + } + + //# Maybe#fantasy-land/extend :: Maybe a ~> (Maybe a -> b) -> Maybe b + //. + //. - `extend (f) (Nothing)` is equivalent to `Nothing` + //. - `extend (f) (Just (x))` is equivalent to `Just (f (Just (x)))` + //. + //. ```javascript + //. > S.extend (S.reduce (S.add) (1)) (Nothing) + //. Nothing + //. + //. > S.extend (S.reduce (S.add) (1)) (Just (99)) + //. Just (100) + //. ``` + function Nothing$prototype$extend(f) { + return this; + } + function Just$prototype$extend(f) { + return Just (f (this)); + } + + return Maybe; + +})); + +//. [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..8921ccc --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "sanctuary-maybe", + "version": "0.0.0", + "description": "Fantasy Land -compliant Maybe type", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/sanctuary-js/sanctuary-maybe.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-either": "1.0.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..3985cea --- /dev/null +++ b/test/index.js @@ -0,0 +1,449 @@ +'use strict'; + +const assert = require ('assert'); + +const laws = require ('fantasy-laws'); +const jsc = require ('jsverify'); +const Either = require ('sanctuary-either'); +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 Maybe = require ('..'); + + +const {Nothing, Just} = Maybe; + + +// EitherArb :: Arbitrary a -> Arbitrary b -> Arbitrary (Either a b) +const EitherArb = arbL => arbR => + jsc.oneof (arbL.smap (Either.Left, left => left.value, show), + arbR.smap (Either.Right, right => right.value, show)); + +// IdentityArb :: Arbitrary a -> Arbitrary (Identity a) +const IdentityArb = arb => arb.smap (Identity, Z.extract, show); + +// MaybeArb :: Arbitrary a -> Arbitrary (Maybe a) +const MaybeArb = arb => + jsc.oneof (jsc.constant (Nothing), + arb.smap (Just, just => just.value, show)); + +// eitherToMaybe :: Either a b -> Maybe b +const eitherToMaybe = e => Z.reduce ((_, x) => Just (x), Nothing, e); + +// head :: Array a -> Maybe a +const head = xs => xs.length === 0 ? Nothing : Just (xs[0]); + +// parseFloat_ :: String -> Maybe Number +const parseFloat_ = s => Z.reject (isNaN, Just (parseFloat (s))); + +// 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 ('Maybe', () => { + + test ('metadata', () => { + eq (typeof Nothing) ('object'); + eq (typeof Just) ('function'); + eq (Just.length) (1); + }); + + test ('tags', () => { + const just = Just (0); + eq (Nothing.isNothing) (true); + eq (Nothing.isJust) (false); + eq (just.isNothing) (false); + eq (just.isJust) (true); + }); + + test ('@@type', () => { + eq (type (Nothing)) ('sanctuary-maybe/Maybe@1'); + eq (type (Just (0))) ('sanctuary-maybe/Maybe@1'); + eq (type.parse (type (Just (0)))) + ({namespace: 'sanctuary-maybe', name: 'Maybe', version: 1}); + }); + + test ('@@show', () => { + eq (show (Nothing)) ('Nothing'); + eq (show (Just (['foo', 'bar', 'baz']))) ('Just (["foo", "bar", "baz"])'); + eq (show (Just (Just (Just (-0))))) ('Just (Just (Just (-0)))'); + }); + +}); + +suite ('type-class predicates', () => { + + test ('Setoid', () => { + eq (Z.Setoid.test (Nothing)) (true); + eq (Z.Setoid.test (Just (Useless))) (false); + eq (Z.Setoid.test (Just (/(?:)/))) (true); + }); + + test ('Ord', () => { + eq (Z.Ord.test (Nothing)) (true); + eq (Z.Ord.test (Just (Useless))) (false); + eq (Z.Ord.test (Just (/(?:)/))) (false); + eq (Z.Ord.test (Just (0))) (true); + }); + + test ('Semigroupoid', () => { + eq (Z.Semigroupoid.test (Nothing)) (false); + eq (Z.Semigroupoid.test (Just ([]))) (false); + }); + + test ('Category', () => { + eq (Z.Category.test (Nothing)) (false); + eq (Z.Category.test (Just ([]))) (false); + }); + + test ('Semigroup', () => { + eq (Z.Semigroup.test (Nothing)) (true); + eq (Z.Semigroup.test (Just (Useless))) (false); + eq (Z.Semigroup.test (Just (0))) (false); + eq (Z.Semigroup.test (Just ([]))) (true); + }); + + test ('Monoid', () => { + eq (Z.Monoid.test (Nothing)) (true); + eq (Z.Monoid.test (Just (Useless))) (false); + eq (Z.Monoid.test (Just (0))) (false); + eq (Z.Monoid.test (Just ([]))) (true); + }); + + test ('Group', () => { + eq (Z.Group.test (Nothing)) (false); + eq (Z.Group.test (Just ([]))) (false); + }); + + test ('Filterable', () => { + eq (Z.Filterable.test (Nothing)) (true); + eq (Z.Filterable.test (Just (Useless))) (true); + }); + + test ('Functor', () => { + eq (Z.Functor.test (Nothing)) (true); + eq (Z.Functor.test (Just (Useless))) (true); + }); + + test ('Bifunctor', () => { + eq (Z.Bifunctor.test (Nothing)) (false); + eq (Z.Bifunctor.test (Just ([]))) (false); + }); + + test ('Profunctor', () => { + eq (Z.Profunctor.test (Nothing)) (false); + eq (Z.Profunctor.test (Just (Math.sqrt))) (false); + }); + + test ('Apply', () => { + eq (Z.Apply.test (Nothing)) (true); + eq (Z.Apply.test (Just (Useless))) (true); + }); + + test ('Applicative', () => { + eq (Z.Applicative.test (Nothing)) (true); + eq (Z.Applicative.test (Just (Useless))) (true); + }); + + test ('Chain', () => { + eq (Z.Chain.test (Nothing)) (true); + eq (Z.Chain.test (Just (Useless))) (true); + }); + + test ('ChainRec', () => { + eq (Z.ChainRec.test (Nothing)) (true); + eq (Z.ChainRec.test (Just (Useless))) (true); + }); + + test ('Monad', () => { + eq (Z.Monad.test (Nothing)) (true); + eq (Z.Monad.test (Just (Useless))) (true); + }); + + test ('Alt', () => { + eq (Z.Alt.test (Nothing)) (true); + eq (Z.Alt.test (Just (Useless))) (true); + }); + + test ('Plus', () => { + eq (Z.Plus.test (Nothing)) (true); + eq (Z.Plus.test (Just (Useless))) (true); + }); + + test ('Alternative', () => { + eq (Z.Alternative.test (Nothing)) (true); + eq (Z.Alternative.test (Just (Useless))) (true); + }); + + test ('Foldable', () => { + eq (Z.Foldable.test (Nothing)) (true); + eq (Z.Foldable.test (Just (Useless))) (true); + }); + + test ('Traversable', () => { + eq (Z.Traversable.test (Nothing)) (true); + eq (Z.Traversable.test (Just (Useless))) (true); + }); + + test ('Extend', () => { + eq (Z.Extend.test (Nothing)) (true); + eq (Z.Extend.test (Just (Useless))) (true); + }); + + test ('Comonad', () => { + eq (Z.Comonad.test (Nothing)) (false); + eq (Z.Comonad.test (Just (Identity (0)))) (false); + }); + + test ('Contravariant', () => { + eq (Z.Contravariant.test (Nothing)) (false); + eq (Z.Contravariant.test (Just (Math.sqrt))) (false); + }); + +}); + +suite ('Setoid laws', () => { + testLaws (laws.Setoid) ({ + reflexivity: [ + MaybeArb (jsc.falsy), + ], + symmetry: [ + MaybeArb (jsc.bool), + MaybeArb (jsc.bool), + ], + transitivity: [ + MaybeArb (jsc.bool), + MaybeArb (jsc.bool), + MaybeArb (jsc.bool), + ], + }); +}); + +suite ('Ord laws', () => { + testLaws (laws.Ord) ({ + totality: [ + MaybeArb (jsc.string), + MaybeArb (jsc.string), + ], + antisymmetry: [ + MaybeArb (jsc.string), + MaybeArb (jsc.string), + MaybeArb (jsc.string), + ], + transitivity: [ + MaybeArb (jsc.string), + MaybeArb (jsc.string), + MaybeArb (jsc.string), + ], + }); +}); + +suite ('Semigroup laws', () => { + testLaws (laws.Semigroup (Z.equals)) ({ + associativity: [ + MaybeArb (jsc.string), + MaybeArb (jsc.string), + MaybeArb (jsc.string), + ], + }); +}); + +suite ('Monoid laws', () => { + testLaws (laws.Monoid (Z.equals, Maybe)) ({ + leftIdentity: [ + MaybeArb (jsc.string), + ], + rightIdentity: [ + MaybeArb (jsc.string), + ], + }); +}); + +suite ('Filterable laws', () => { + testLaws (laws.Filterable (Z.equals)) ({ + distributivity: [ + MaybeArb (jsc.number), + jsc.constant (x => x > -10), + jsc.constant (x => x < 10), + ], + identity: [ + MaybeArb (jsc.number), + ], + annihilation: [ + MaybeArb (jsc.number), + MaybeArb (jsc.number), + ], + }); +}); + +suite ('Functor laws', () => { + testLaws (laws.Functor (Z.equals)) ({ + identity: [ + MaybeArb (jsc.number), + ], + composition: [ + MaybeArb (jsc.number), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], + }); +}); + +suite ('Apply laws', () => { + testLaws (laws.Apply (Z.equals)) ({ + composition: [ + MaybeArb (jsc.constant (Math.sqrt)), + MaybeArb (jsc.constant (Math.abs)), + MaybeArb (jsc.number), + ], + }); +}); + +suite ('Applicative laws', () => { + testLaws (laws.Applicative (Z.equals, Maybe)) ({ + identity: [ + MaybeArb (jsc.number), + ], + homomorphism: [ + jsc.constant (Math.abs), + jsc.number, + ], + interchange: [ + MaybeArb (jsc.constant (Math.abs)), + jsc.number, + ], + }); +}); + +suite ('Chain laws', () => { + testLaws (laws.Chain (Z.equals)) ({ + associativity: [ + MaybeArb (jsc.array (jsc.asciistring)), + jsc.constant (head), + jsc.constant (parseFloat_), + ], + }); +}); + +suite ('ChainRec laws', () => { + testLaws (laws.ChainRec (Z.equals, Maybe)) ({ + equivalence: [ + jsc.constant (x => x >= 1000), + jsc.constant (x => x <= 1 ? Nothing : Just (x * x)), + jsc.constant (Just), + jsc.integer, + ], + }); +}); + +suite ('Monad laws', () => { + testLaws (laws.Monad (Z.equals, Maybe)) ({ + leftIdentity: [ + jsc.constant (head), + jsc.array (jsc.number), + ], + rightIdentity: [ + MaybeArb (jsc.number), + ], + }); +}); + +suite ('Alt laws', () => { + testLaws (laws.Alt (Z.equals)) ({ + associativity: [ + MaybeArb (jsc.number), + MaybeArb (jsc.number), + MaybeArb (jsc.number), + ], + distributivity: [ + MaybeArb (jsc.number), + MaybeArb (jsc.number), + jsc.constant (Math.sqrt), + ], + }); +}); + +suite ('Plus laws', () => { + testLaws (laws.Plus (Z.equals, Maybe)) ({ + leftIdentity: [ + MaybeArb (jsc.number), + ], + rightIdentity: [ + MaybeArb (jsc.number), + ], + annihilation: [ + jsc.constant (Math.sqrt), + ], + }); +}); + +suite ('Alternative laws', () => { + testLaws (laws.Alternative (Z.equals, Maybe)) ({ + distributivity: [ + MaybeArb (jsc.number), + MaybeArb (jsc.constant (Math.sqrt)), + MaybeArb (jsc.constant (Math.abs)), + ], + annihilation: [ + MaybeArb (jsc.number), + ], + }); +}); + +suite ('Foldable laws', () => { + testLaws (laws.Foldable (Z.equals)) ({ + associativity: [ + jsc.constant ((x, y) => x + y), + jsc.number, + MaybeArb (jsc.number), + ], + }); +}); + +suite ('Traversable laws', () => { + testLaws (laws.Traversable (Z.equals)) ({ + naturality: [ + jsc.constant (Either), + jsc.constant (Maybe), + jsc.constant (eitherToMaybe), + MaybeArb (EitherArb (jsc.string) (jsc.number)), + ], + identity: [ + jsc.constant (Identity), + MaybeArb (jsc.number), + ], + composition: [ + jsc.constant (Identity), + jsc.constant (Maybe), + MaybeArb (IdentityArb (MaybeArb (jsc.number))), + ], + }); +}); + +suite ('Extend laws', () => { + testLaws (laws.Extend (Z.equals)) ({ + associativity: [ + MaybeArb (jsc.integer), + jsc.constant (maybe => Z.reduce ((x, y) => x + y, 1, maybe)), + jsc.constant (maybe => Z.reduce ((x, y) => y * y, 1, maybe)), + ], + }); +}); 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