diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..0960fae --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,19 @@ +version: 2.1 + +orbs: + node: circleci/node@6.3.0 + +workflows: + test: + jobs: + - node/test: + setup: + # derive cache key from package.json + - run: cp package.json package-lock.json + override-ci-command: rm package-lock.json && npm install && git checkout -- package.json + matrix: + parameters: + version: + - 18.0.0 + - 20.0.0 + - 22.0.0 diff --git a/.config b/.config new file mode 100644 index 0000000..68751e9 --- /dev/null +++ b/.config @@ -0,0 +1,3 @@ +repo-owner = sanctuary-js +repo-name = sanctuary-constant +contributing-file = .github/CONTRIBUTING.md diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..243d513 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "root": true, + "extends": ["./node_modules/sanctuary-style/eslint.json"], + "parserOptions": {"ecmaVersion": 2020, "sourceType": "module"}, + "globals": {"globalThis": "readonly"} +} diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..e22d931 --- /dev/null +++ b/.github/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 main branch: + + $ git checkout main + $ git pull upstream main + +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/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..ef08538 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [davidchambers] 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/LICENSE b/LICENSE new file mode 100644 index 0000000..ab65140 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2019 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..7329f54 --- /dev/null +++ b/index.js @@ -0,0 +1,283 @@ +/* _____ _____ _____ _____ ______ _____ _____ ______ */ +/* | | | | | | | | | | | | | | | | */ +/* | |__| | | | | | | | |__| '-, ,-' | | | | | | '-, ,-' */ +/* | |__ | | | | | | |__ | | | | | | | | | | */ +/* | | | | | | | | | | | | | | | | | | | | | | */ +/* |_____| |_____| |__|__| |_____| |__| |__|__| |__|__| |__| */ + +//. Fantasy Land +//. +//. # sanctuary-constant +//. +//. A value of type `Constant a b` always contains exactly one value, +//. of type `a`. Mapping over a `Constant a b` has no effect because +//. the `b -> c` function is never applied. + +import show from 'sanctuary-show'; +import Z from 'sanctuary-type-classes'; + +export {Constant}; + +const constantTypeIdent = 'sanctuary-constant/Constant@1'; + +//. ```javascript +//. > import S from 'sanctuary' +//. > import $ from 'sanctuary-def' +//. > import Useless from 'sanctuary-useless' +//. ``` +//. +//. `Constant a b` satisfies the following [Fantasy Land][] specifications: +//. +//. ```javascript +//. > S.map (k => +//. . k + ' '.repeat (16 - k.length) + +//. . (Z[k].test (Constant (Useless.constructor) (Useless)) ? '\u2705 ' : +//. . Z[k].test (Constant (Array) (['foo', 'bar', 'baz'])) ? '\u2705 * ' : +//. . /* otherwise */ '\u274C ') +//. . ) (S.keys (S.unchecked.filter (S.is ($.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 ✅ * ', // if ‘a’ satisfies Semigroup +//. . 'Applicative ✅ * ', // if ‘a’ satisfies Monoid +//. . 'Chain ❌ ', +//. . 'ChainRec ❌ ', +//. . 'Monad ❌ ', +//. . 'Alt ❌ ', +//. . 'Plus ❌ ', +//. . 'Alternative ❌ ', +//. . 'Foldable ✅ ', +//. . 'Traversable ✅ ', +//. . 'Extend ❌ ', +//. . 'Comonad ❌ ', +//. . 'Contravariant ❌ ' ] +//. ``` + +//# Constant :: TypeRep a -> a -> Constant a b +//. +//. Constant's sole data constructor. The [type representative][] makes +//. `Constant (M)` an [Applicative][]-compatible type representative if +//. `M` represents some monoidal type. +//. +//. ```javascript +//. > Constant (String) ('abc') +//. Constant (String) ('abc') +//. +//. > Constant (Number) (123) +//. Constant (Number) (123) +//. ``` +const Constant = A => { + const prototype = { + /* eslint-disable key-spacing */ + 'constructor': Constant$bound, + '@@type': constantTypeIdent, + '@@show': Constant$prototype$show, + 'fantasy-land/map': Constant$prototype$map, + 'fantasy-land/bimap': Constant$prototype$bimap, + 'fantasy-land/reduce': Constant$prototype$reduce, + 'fantasy-land/traverse': Constant$prototype$traverse, + /* eslint-enable key-spacing */ + }; + + if (globalThis.process?.versions?.node != null) { + const inspect = Symbol.for ('nodejs.util.inspect.custom'); + prototype[inspect] = Constant$prototype$show; + } + + /* c8 ignore start */ + if (typeof globalThis.Deno?.customInspect === 'symbol') { + const inspect = globalThis.Deno.customInspect; + prototype[inspect] = Constant$prototype$show; + } + /* c8 ignore stop */ + + function Constant$bound(value) { + const constant = Object.create (prototype); + if (Z.Setoid.test (value)) { + constant['fantasy-land/equals'] = Constant$prototype$equals; + if (Z.Ord.test (value)) { + constant['fantasy-land/lte'] = Constant$prototype$lte; + } + } + if (Z.Semigroup.test (value)) { + constant['fantasy-land/concat'] = Constant$prototype$concat; + constant['fantasy-land/ap'] = Constant$prototype$ap; + } + constant.value = value; + return constant; + } + + // XXX: sanctuary-show@3 only respects @@show on "objects". Fool it. + Constant$bound[Symbol.toStringTag] = 'Object'; + + Constant$bound['@@show'] = () => { + if (!(Object.prototype.hasOwnProperty.call (A, 'name'))) { + const source = String (A); + const match = /^\s*function ([$_A-Za-z][$_A-Za-z0-9]*)/.exec (source); + return 'Constant (' + (match == null ? source : match[1]) + ')'; + } else if (A.name === Constant$bound.name) { + return 'Constant (' + show (A) + ')'; + } else { + return 'Constant (' + A.name + ')'; + } + }; + + //# Constant.fantasy-land/of :: Monoid m => a -> Constant m a + //. + //. `of (Constant (M)) (x)` is equivalent to `Constant (M) (empty (M))`. + //. + //. ```javascript + //. > S.of (Constant (Array)) (42) + //. Constant (Array) ([]) + //. + //. > S.of (Constant (String)) (42) + //. Constant (String) ('') + //. ``` + (() => { + let empty; + try { empty = Z.empty (A); } catch (err) { return; } + Constant$bound['fantasy-land/of'] = x => Constant$bound (empty); + }) (); + + //# Constant#@@show :: Showable a => Constant a b ~> () -> String + //. + //. `show (Constant (A) (x))` is equivalent to + //. `'Constant (' + A.name + ') (' + show (x) + ')'`. + //. + //. ```javascript + //. > S.show (Constant (Array) (['foo', 'bar', 'baz'])) + //. 'Constant (Array) (["foo", "bar", "baz"])' + //. ``` + function Constant$prototype$show() { + return show (Constant$bound) + ' (' + show (this.value) + ')'; + } + + //# Constant#fantasy-land/equals :: Setoid a => Constant a b ~> Constant a b -> Boolean + //. + //. `Constant (A) (x)` is equal to `Constant (A) (y)` [iff][] `x` is + //. equal to `y` according to [`Z.equals`][]. + //. + //. ```javascript + //. > S.equals (Constant (Array) ([1, 2, 3])) + //. . (Constant (Array) ([1, 2, 3])) + //. true + //. + //. > S.equals (Constant (Array) ([1, 2, 3])) + //. . (Constant (Array) ([3, 2, 1])) + //. false + //. ``` + function Constant$prototype$equals(other) { + return Z.equals (this.value, other.value); + } + + //# Constant#fantasy-land/lte :: Ord a => Constant a b ~> Constant a b -> Boolean + //. + //. `Constant (A) (x)` is less than or equal to `Constant (A) (y)` [iff][] + //. `x` is less than or equal to `y` according to [`Z.lte`][]. + //. + //. ```javascript + //. > S.filter (S.lte (Constant (Number) (1))) + //. . ([Constant (Number) (0), + //. . Constant (Number) (1), + //. . Constant (Number) (2)]) + //. [Constant (Number) (0), Constant (Number) (1)] + //. ``` + function Constant$prototype$lte(other) { + return Z.lte (this.value, other.value); + } + + //# Constant#fantasy-land/concat :: Semigroup a => Constant a b ~> Constant a b -> Constant a b + //. + //. `concat (Constant (A) (x)) (Constant (A) (y))` is equivalent to + //. `Constant (A) (concat (x) (y))`. + //. + //. ```javascript + //. > S.concat (Constant (Array) ([1, 2, 3])) + //. . (Constant (Array) ([4, 5, 6])) + //. Constant (Array) ([1, 2, 3, 4, 5, 6]) + //. ``` + function Constant$prototype$concat(other) { + return Constant$bound (Z.concat (this.value, other.value)); + } + + //# Constant#fantasy-land/map :: Constant a b ~> (b -> c) -> Constant a c + //. + //. `map (f) (Constant (A) (x))` is equivalent to `Constant (A) (x)`. + //. + //. ```javascript + //. > S.map (Math.sqrt) (Constant (Number) (64)) + //. Constant (Number) (64) + //. ``` + function Constant$prototype$map(f) { + return this; + } + + //# Constant#fantasy-land/bimap :: Constant a c ~> (a -> b, c -> d) -> Constant b d + //. + //. `bimap (f) (g) (Constant (A) (x))` is equivalent to + //. `Constant (A) (f (x))`. + //. + //. ```javascript + //. > S.bimap (s => s.length) (Math.sqrt) (Constant (String) ('abc')) + //. Constant (Number) (3) + //. ``` + function Constant$prototype$bimap(f, g) { + const x = f (this.value); + return Constant (x.constructor) (x); + } + + //# Constant#fantasy-land/ap :: Semigroup a => Constant a b ~> Constant a (b -> c) -> Constant a c + //. + //. `ap (Constant (A) (x)) (Constant (A) (y))` is equivalent to + //. `concat (Constant (A) (x)) (Constant (A) (y))`. + //. + //. ```javascript + //. > S.ap (Constant (Array) ([1, 2, 3])) (Constant (Array) ([4, 5, 6])) + //. Constant (Array) ([1, 2, 3, 4, 5, 6]) + //. ``` + function Constant$prototype$ap(other) { + return Z.concat (other, this); + } + + //# Constant#fantasy-land/reduce :: Constant a b ~> ((c, b) -> c, c) -> c + //. + //. `reduce (f) (x) (Constant (A) (y))` is equivalent to `x`. + //. + //. ```javascript + //. > S.reduce (S.add) (100) (Constant (Number) (42)) + //. 100 + //. ``` + function Constant$prototype$reduce(f, x) { + return x; + } + + //# Constant#fantasy-land/traverse :: Applicative f => Constant a b ~> (TypeRep f, b -> f c) -> f (Constant a c) + //. + //. `traverse (A) (f) (Constant (X) (x))` is equivalent to + //. `of (A) (Constant (X) (x))`. + //. + //. ```javascript + //. > S.traverse (Array) (Math.sqrt) (Constant (Number) (64)) + //. [Constant (Number) (64)] + //. ``` + function Constant$prototype$traverse(typeRep, f) { + return Z.of (typeRep, this); + } + + return Constant$bound; +}; + +//. [Applicative]: v:fantasyland/fantasy-land#Applicative +//. [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 representative]: v:fantasyland/fantasy-land#type-representatives diff --git a/package.json b/package.json new file mode 100644 index 0000000..7cf6e62 --- /dev/null +++ b/package.json @@ -0,0 +1,45 @@ +{ + "name": "sanctuary-constant", + "version": "0.0.0", + "description": "Fantasy Land -compliant Constant type", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/sanctuary-js/sanctuary-constant.git" + }, + "type": "module", + "exports": { + ".": "./index.js", + "./package.json": "./package.json" + }, + "files": [ + "/LICENSE", + "/README.md", + "/index.js", + "/package.json" + ], + "engines": { + "node": ">=16.0.0" + }, + "dependencies": { + "sanctuary-show": "3.0.0", + "sanctuary-type-classes": "13.0.0" + }, + "devDependencies": { + "fantasy-land": "5.0.0", + "fantasy-laws": "1.2.x", + "jsverify": "0.8.x", + "sanctuary": "3.1.0", + "sanctuary-def": "0.22.0", + "sanctuary-identity": "2.1.x", + "sanctuary-scripts": "7.0.x", + "sanctuary-type-identifiers": "3.0.0", + "sanctuary-useless": "2.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/index.js b/test/index.js new file mode 100644 index 0000000..a3fc6e5 --- /dev/null +++ b/test/index.js @@ -0,0 +1,331 @@ +import {deepStrictEqual as eq} from 'node:assert'; +import {inspect} from 'node:util'; + +import laws from 'fantasy-laws'; +import jsc from 'jsverify'; +import test from 'oletus'; +import Identity from 'sanctuary-identity'; +import show from 'sanctuary-show'; +import Z from 'sanctuary-type-classes'; +import type from 'sanctuary-type-identifiers'; +import Useless from 'sanctuary-useless'; + +import {Constant} from '../index.js'; + + +// ConstantArb :: Monoid m => TypeRep m -> Arbitrary a -> Arbitrary (Constant a b) +const ConstantArb = M => arb => arb.smap (Constant (M), c => c.value, show); + +// 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 :: String -> Object -> Object -> Undefined +const testLaws = typeClass => laws => arbs => { + (Object.keys (laws)).forEach (name => { + eq (laws[name].length, arbs[name].length); + const prettyName = name.replace (/[A-Z]/g, c => ' ' + c.toLowerCase ()); + test (`${typeClass} laws \x1B[2m›\x1B[22m ${prettyName}`, + laws[name] (...arbs[name])); + }); +}; + + +test ('metadata', () => { + eq (typeof Constant, 'function'); + eq (Constant.name, 'Constant'); + eq (Constant.length, 1); +}); + +test ('@@type', () => { + eq (type (Constant (Number) (0)), 'sanctuary-constant/Constant@1'); + eq (type.parse (type (Constant (Number) (0))), + {namespace: 'sanctuary-constant', name: 'Constant', version: 1}); +}); + +test ('@@show', () => { + eq (show (Constant (Array) (['foo', 'bar', 'baz'])), + 'Constant (Array) (["foo", "bar", "baz"])'); + eq (show (Constant (Constant (Constant (Number))) + (Constant (Constant (Number)) + (Constant (Number) + (-0)))), + 'Constant (Constant (Constant (Number)))' + + ' (Constant (Constant (Number))' + + ' (Constant (Number)' + + ' (-0)))'); + + // Foo :: String -> Foo + function Foo(value) { + return { + 'constructor': Foo, + '@@show': function Foo$show() { return 'Foo (' + show (value) + ')'; }, + }; + } + delete Foo.name; + eq (show (Constant (Foo) (Foo ('foo'))), + 'Constant (Foo) (Foo ("foo"))'); + + // Bar :: String -> Bar + const Bar = value => ({ + 'constructor': Bar, + '@@show': function Bar$show() { return 'Bar (' + show (value) + ')'; }, + }); + delete Bar.name; + eq (show (Constant (Bar) (Bar ('bar'))), + 'Constant (' + String (Bar) + ') (Bar ("bar"))'); +}); + +test ('util.inspect', () => { + eq (inspect (Constant (Array) (['foo', 'bar', 'baz'])), + 'Constant (Array) (["foo", "bar", "baz"])'); + eq (inspect (Constant (Constant (Constant (Number))) + (Constant (Constant (Number)) + (Constant (Number) + (-0)))), + 'Constant (Constant (Constant (Number)))' + + ' (Constant (Constant (Number))' + + ' (Constant (Number)' + + ' (-0)))'); +}); + +test ('Setoid', () => { + eq (Z.Setoid.test (Constant (Useless.constructor) (Useless)), false); + eq (Z.Setoid.test (Constant (RegExp) (/(?:)/)), true); +}); + +test ('Ord', () => { + eq (Z.Ord.test (Constant (Useless.constructor) (Useless)), false); + eq (Z.Ord.test (Constant (RegExp) (/(?:)/)), false); + eq (Z.Ord.test (Constant (Number) (0)), true); +}); + +test ('Semigroupoid', () => { + eq (Z.Semigroupoid.test (Constant (Array) ([])), false); +}); + +test ('Category', () => { + eq (Z.Category.test (Constant (Array) ([])), false); +}); + +test ('Semigroup', () => { + eq (Z.Semigroup.test (Constant (Useless.constructor) (Useless)), false); + eq (Z.Semigroup.test (Constant (Number) (0)), false); + eq (Z.Semigroup.test (Constant (Array) ([])), true); +}); + +test ('Monoid', () => { + eq (Z.Monoid.test (Constant (Array) ([])), false); +}); + +test ('Group', () => { + eq (Z.Group.test (Constant (Array) ([])), false); +}); + +test ('Filterable', () => { + eq (Z.Filterable.test (Constant (Array) ([])), false); +}); + +test ('Functor', () => { + eq (Z.Functor.test (Constant (Useless.constructor) (Useless)), true); +}); + +test ('Bifunctor', () => { + eq (Z.Bifunctor.test (Constant (Useless.constructor) (Useless)), true); +}); + +test ('Profunctor', () => { + eq (Z.Profunctor.test (Constant (Function) (Math.sqrt)), false); +}); + +test ('Apply', () => { + eq (Z.Apply.test (Constant (Useless.constructor) (Useless)), false); + eq (Z.Apply.test (Constant (Number) (0)), false); + eq (Z.Apply.test (Constant (Array) ([])), true); +}); + +test ('Applicative', () => { + eq (Z.Applicative.test (Constant (Useless.constructor) (Useless)), false); + eq (Z.Applicative.test (Constant (Number) (0)), false); + eq (Z.Applicative.test (Constant (Array) ([])), true); +}); + +test ('Chain', () => { + eq (Z.Chain.test (Constant (Array) ([])), false); +}); + +test ('ChainRec', () => { + eq (Z.ChainRec.test (Constant (Array) ([])), false); +}); + +test ('Monad', () => { + eq (Z.Monad.test (Constant (Array) ([])), false); +}); + +test ('Alt', () => { + eq (Z.Alt.test (Constant (Array) ([])), false); +}); + +test ('Plus', () => { + eq (Z.Plus.test (Constant (Array) ([])), false); +}); + +test ('Alternative', () => { + eq (Z.Alternative.test (Constant (Array) ([])), false); +}); + +test ('Foldable', () => { + eq (Z.Foldable.test (Constant (Useless.constructor) (Useless)), true); +}); + +test ('Traversable', () => { + eq (Z.Traversable.test (Constant (Useless.constructor) (Useless)), true); +}); + +test ('Extend', () => { + eq (Z.Extend.test (Constant (Array) ([])), false); +}); + +test ('Comonad', () => { + eq (Z.Comonad.test (Constant (Array) ([])), false); +}); + +test ('Contravariant', () => { + eq (Z.Contravariant.test (Constant (Function) (Math.sqrt)), false); +}); + +testLaws ('Setoid') (laws.Setoid) ({ + reflexivity: [ + ConstantArb (Number) (NumberArb), + ], + symmetry: [ + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ], + transitivity: [ + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ], +}); + +testLaws ('Ord') (laws.Ord) ({ + totality: [ + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ], + antisymmetry: [ + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ], + transitivity: [ + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ConstantArb (Number) (NumberArb), + ], +}); + +testLaws ('Semigroup') (laws.Semigroup (Z.equals)) ({ + associativity: [ + ConstantArb (String) (jsc.string), + ConstantArb (String) (jsc.string), + ConstantArb (String) (jsc.string), + ], +}); + +testLaws ('Functor') (laws.Functor (Z.equals)) ({ + identity: [ + ConstantArb (String) (jsc.string), + ], + composition: [ + ConstantArb (String) (jsc.string), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], +}); + +testLaws ('Bifunctor') (laws.Bifunctor (Z.equals)) ({ + identity: [ + ConstantArb (String) (jsc.string), + ], + composition: [ + ConstantArb (String) (jsc.string), + jsc.constant (Math.sqrt), + jsc.constant (s => s.length), + jsc.constant (Math.sqrt), + jsc.constant (Math.abs), + ], +}); + +testLaws ('Apply') (laws.Apply (Z.equals)) ({ + composition: [ + ConstantArb (String) (jsc.string), + ConstantArb (String) (jsc.string), + ConstantArb (String) (jsc.string), + ], +}); + +testLaws ('Applicative') (laws.Applicative (Z.equals, Constant (Array))) ({ + identity: [ + ConstantArb (Array) (jsc.array (jsc.number)), + ], + homomorphism: [ + jsc.constant (Math.abs), + jsc.number, + ], + interchange: [ + ConstantArb (Array) (jsc.array (jsc.number)), + jsc.string, + ], +}); + +testLaws ('Foldable') (laws.Foldable (Z.equals)) ({ + associativity: [ + jsc.constant (s => x => s + s), + jsc.string, + ConstantArb (Number) (jsc.number), + ], +}); + +testLaws ('Traversable') (laws.Traversable (Z.equals)) ({ + naturality: [ + jsc.constant (Array), + jsc.constant (Identity), + jsc.constant (xs => Identity (xs[0])), + ConstantArb (Array) (NonEmpty (jsc.array (jsc.number))), + ], + identity: [ + jsc.constant (Array), + ConstantArb (String) (jsc.string), + ], + composition: [ + jsc.constant (Array), + jsc.constant (Identity), + ConstantArb (Array) (jsc.array (IdentityArb (jsc.string))), + ], +});