├── .config ├── .eslintrc.json ├── .gitignore ├── .npmrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc.json └── concurrify.test.js /.config: -------------------------------------------------------------------------------- 1 | author-name = Aldwin Vlasblom 2 | repo-owner = fluture-js 3 | repo-name = concurrify 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["./node_modules/sanctuary-style/eslint-es3.json"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.0.0" 4 | - "6" 5 | - "7" 6 | - "8" 7 | - "9" 8 | - "10" 9 | after_success: ./node_modules/.bin/codecov 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guide 2 | 3 | ## Making a contribution 4 | 5 | * Fork and clone the project 6 | * Commit changes to a branch named after the work that was done 7 | * Make sure the tests pass locally 8 | * Create a pull request 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2019 Aldwin Vlasblom 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Concurrify 2 | 3 | ## :warning: Unmaintained 4 | 5 | This library is no longer used by Fluture ([`7b6d9fd`][]), and now fills a 6 | space I don't think is worth filling. 7 | 8 | 1. It's overly opinionated as a result of having been part of Fluture. 9 | 2. It does more than strictly necessary (inclusion of Alternative instance). 10 | 3. It doesn't do any real work - it just takes all the needed functions as 11 | input and wires them up for Fantasy Land compliance. As such, it's not 12 | a lot of effort for users to do the wiring themselves. 13 | 4. It used undocumented features from [Sanctuary Type Identifiers][STI] to 14 | automatically generate new type identifiers, but these are no longer 15 | available in the latest version of Sanctuary Type Identifiers. 16 | 17 | [`7b6d9fd`]: https://github.com/fluture-js/Fluture/commit/7b6d9fdc4ebbc4c6c2485cb5a8d1b2da1eb39fe4 18 | ---- 19 | 20 | ## Introduction 21 | 22 | Turn non-concurrent [FantasyLand 3][FL3] Applicatives concurrent. 23 | 24 | Most time-dependent applicatives are very useful as Monads, because it 25 | gives them the ability to run sequentially, where each step depends on the 26 | previous. However, they lose the ability to run concurrently. This library 27 | allows one to wrap a [`Monad`][FL:Monad] (with sequential `ap`) in an 28 | [`Alternative`][FL:Alternative] (with parallel `ap`). 29 | 30 | ## Usage 31 | 32 | ```js 33 | // The concurrify function takes four arguments, explained below. 34 | const concurrify = require ('concurrify'); 35 | 36 | // The Type Representative of the Applicative we want to transform. 37 | const Future = require ('fluture'); 38 | 39 | // A "zero" instance and an "alt" function for "Alternative". 40 | const zero = Future (() => {}); 41 | const alt = Future.race; 42 | 43 | // An override "ap" function that runs the Applicatives concurrently. 44 | const ap = (mx, mf) => (Future.both (mx, mf)).map (([x, f]) => f (x)); 45 | 46 | // A new Type Representative created by concurrify. 47 | const ConcurrentFuture = concurrify (Future, zero, alt, ap); 48 | 49 | // We can use our type as such: 50 | const par = ConcurrentFuture (Future.of (1)); 51 | const seq = par.sequential; 52 | seq.fork (console.error, console.log); 53 | ``` 54 | 55 | ## Interoperability 56 | 57 | * Implements [FantasyLand 3][FL3] `Alternative` 58 | (`of`, `zero`, `map`, `ap`, `alt`). 59 | * Instances can be identified by, and are compared using, 60 | [Sanctuary Type Identifiers][STI]. 61 | * Instances can be converted to String representations according to 62 | [Sanctuary Show][SS]. 63 | 64 | ## API 65 | 66 | #### `concurrify :: (Applicative f, Alternative (m f)) => (TypeRep f, f a, (f a, f a) -⁠> f a, (f a, f (a -⁠> b)) -⁠> f b) -⁠> f c -⁠> m f c` 67 | 68 | [FL3]: https://github.com/fantasyland/fantasy-land/ 69 | [FL:Monad]: https://github.com/fantasyland/fantasy-land/#monad 70 | [FL:Alternative]: https://github.com/fantasyland/fantasy-land/#alternative 71 | [STI]: https://github.com/sanctuary-js/sanctuary-type-identifiers 72 | [SS]: https://github.com/sanctuary-js/sanctuary-show 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //. # Concurrify 2 | //. 3 | //. [![Chat](https://badges.gitter.im/fluture-js/concurrify.svg)](https://gitter.im/fluture-js/fluture) 4 | //. [![NPM Version](https://badge.fury.io/js/concurrify.svg)](https://www.npmjs.com/package/concurrify) 5 | //. [![Dependencies](https://david-dm.org/fluture-js/concurrify.svg)](https://david-dm.org/fluture-js/concurrify) 6 | //. [![Build Status](https://travis-ci.org/fluture-js/concurrify.svg?branch=master)](https://travis-ci.org/fluture-js/concurrify) 7 | //. [![Code Coverage](https://codecov.io/gh/fluture-js/concurrify/branch/master/graph/badge.svg)](https://codecov.io/gh/fluture-js/concurrify) 8 | //. 9 | //. Turn non-concurrent [FantasyLand 3][FL3] Applicatives concurrent. 10 | //. 11 | //. Most time-dependent applicatives are very useful as Monads, because it 12 | //. gives them the ability to run sequentially, where each step depends on the 13 | //. previous. However, they lose the ability to run concurrently. This library 14 | //. allows one to wrap a [`Monad`][FL:Monad] (with sequential `ap`) in an 15 | //. [`Alternative`][FL:Alternative] (with parallel `ap`). 16 | //. 17 | //. ## Usage 18 | //. 19 | //. ```js 20 | //. // The concurrify function takes four arguments, explained below. 21 | //. const concurrify = require ('concurrify'); 22 | //. 23 | //. // The Type Representative of the Applicative we want to transform. 24 | //. const Future = require ('fluture'); 25 | //. 26 | //. // A "zero" instance and an "alt" function for "Alternative". 27 | //. const zero = Future (() => {}); 28 | //. const alt = Future.race; 29 | //. 30 | //. // An override "ap" function that runs the Applicatives concurrently. 31 | //. const ap = (mx, mf) => (Future.both (mx, mf)).map (([x, f]) => f (x)); 32 | //. 33 | //. // A new Type Representative created by concurrify. 34 | //. const ConcurrentFuture = concurrify (Future, zero, alt, ap); 35 | //. 36 | //. // We can use our type as such: 37 | //. const par = ConcurrentFuture (Future.of (1)); 38 | //. const seq = par.sequential; 39 | //. seq.fork (console.error, console.log); 40 | //. ``` 41 | //. 42 | //. ## Interoperability 43 | //. 44 | //. * Implements [FantasyLand 3][FL3] `Alternative` 45 | //. (`of`, `zero`, `map`, `ap`, `alt`). 46 | //. * Instances can be identified by, and are compared using, 47 | //. [Sanctuary Type Identifiers][STI]. 48 | //. * Instances can be converted to String representations according to 49 | //. [Sanctuary Show][SS]. 50 | //. 51 | //. ## API 52 | (function(f) { 53 | 54 | 'use strict'; 55 | 56 | /* istanbul ignore next */ 57 | if (typeof module === 'object' && typeof module.exports === 'object') { 58 | module.exports = f ( 59 | require ('sanctuary-show'), 60 | require ('sanctuary-type-identifiers') 61 | ); 62 | } else { 63 | self.concurrify = f ( 64 | self.sanctuaryShow, 65 | self.sanctuaryTypeIdentifiers 66 | ); 67 | } 68 | 69 | } (function(show, type) { 70 | 71 | 'use strict'; 72 | 73 | var $alt = 'fantasy-land/alt'; 74 | var $ap = 'fantasy-land/ap'; 75 | var $map = 'fantasy-land/map'; 76 | var $of = 'fantasy-land/of'; 77 | var $zero = 'fantasy-land/zero'; 78 | var $$type = '@@type'; 79 | var $$show = '@@show'; 80 | var ordinal = ['first', 'second', 'third', 'fourth', 'fifth']; 81 | 82 | // isFunction :: Any -> Boolean 83 | function isFunction(f) { 84 | return typeof f === 'function'; 85 | } 86 | 87 | // isBinary :: Function -> Boolean 88 | function isBinary(f) { 89 | return f.length >= 2; 90 | } 91 | 92 | // isApplicativeRepr :: TypeRepr -> Boolean 93 | function isApplicativeRepr(Repr) { 94 | return typeof Repr[$of] === 'function' && 95 | typeof Repr[$of] ()[$ap] === 'function'; 96 | } 97 | 98 | // invalidArgument :: (String, Number, String, String) -> !Undefined 99 | function invalidArgument(it, at, expected, actual) { 100 | throw new TypeError ( 101 | it 102 | + ' expects its ' 103 | + ordinal[at] 104 | + ' argument to ' 105 | + expected 106 | + '\n Actual: ' 107 | + show (actual) 108 | ); 109 | } 110 | 111 | // invalidContext :: (String, String, String) -> !Undefined 112 | function invalidContext(it, actual, an) { 113 | throw new TypeError ( 114 | it 115 | + ' was invoked outside the context of a ' 116 | + an 117 | + '. \n Called on: ' 118 | + show (actual) 119 | ); 120 | } 121 | 122 | // getTypeIdentifier :: TypeRepresentative -> String 123 | function getTypeIdentifier(Repr) { 124 | return Repr[$$type] || Repr.name || 'Anonymous'; 125 | } 126 | 127 | // generateTypeIdentifier :: String -> String 128 | function generateTypeIdentifier(identifier) { 129 | var o = type.parse (identifier); 130 | return ( 131 | (o.namespace || 'concurrify') + '/Concurrent' + o.name + '@' + o.version 132 | ); 133 | } 134 | 135 | //# concurrify :: (Applicative f, Alternative (m f)) => (TypeRep f, f a, (f a, f a) -> f a, (f a, f (a -> b)) -> f b) -> f c -> m f c 136 | return function concurrify(Repr, zero, alt, ap) { 137 | 138 | var INNERTYPE = getTypeIdentifier (Repr); 139 | var OUTERTYPE = generateTypeIdentifier (INNERTYPE); 140 | var INNERNAME = (type.parse (INNERTYPE)).name; 141 | var OUTERNAME = (type.parse (OUTERTYPE)).name; 142 | 143 | function Concurrently(sequential) { 144 | this.sequential = sequential; 145 | } 146 | 147 | function isInner(x) { 148 | return ( 149 | (x instanceof Repr) || 150 | (Boolean (x) && x.constructor === Repr) || 151 | (type (x) === Repr[$$type]) 152 | ); 153 | } 154 | 155 | function isOuter(x) { 156 | return ( 157 | (x instanceof Concurrently) || 158 | (Boolean (x) && x.constructor === Concurrently) || 159 | (type (x) === OUTERTYPE) 160 | ); 161 | } 162 | 163 | function construct(x) { 164 | if (!isInner (x)) { 165 | invalidArgument (OUTERNAME, 0, 'be of type "' + INNERNAME + '"', x); 166 | } 167 | return new Concurrently (x); 168 | } 169 | 170 | if (!isApplicativeRepr (Repr)) { 171 | invalidArgument ('concurrify', 0, 'represent an Applicative', Repr); 172 | } 173 | 174 | if (!isInner (zero)) { 175 | invalidArgument 176 | ('concurrify', 1, 'be of type "' + INNERNAME + '"', zero); 177 | } 178 | 179 | if (!isFunction (alt)) { 180 | invalidArgument ('concurrify', 2, 'be a function', alt); 181 | } 182 | 183 | if (!isBinary (alt)) { 184 | invalidArgument ('concurrify', 2, 'be binary', alt); 185 | } 186 | 187 | if (!isFunction (ap)) { 188 | invalidArgument ('concurrify', 3, 'be a function', ap); 189 | } 190 | 191 | if (!isBinary (ap)) { 192 | invalidArgument ('concurrify', 3, 'be binary', ap); 193 | } 194 | 195 | var proto = 196 | Concurrently.prototype = 197 | construct.prototype = {constructor: construct}; 198 | 199 | proto[$$type] = OUTERTYPE; 200 | construct[$$type] = OUTERTYPE; 201 | 202 | var mzero = new Concurrently (zero); 203 | 204 | construct[$zero] = function Concurrently$zero() { 205 | return mzero; 206 | }; 207 | 208 | construct[$of] = function Concurrently$of(value) { 209 | return new Concurrently (Repr[$of] (value)); 210 | }; 211 | 212 | proto[$map] = function Concurrently$map(mapper) { 213 | if (!isOuter (this)) { 214 | invalidContext (OUTERNAME + '#map', this, OUTERNAME); 215 | } 216 | 217 | if (!isFunction (mapper)) { 218 | invalidArgument (OUTERNAME + '#map', 0, 'be a function', mapper); 219 | } 220 | 221 | return new Concurrently (this.sequential[$map] (mapper)); 222 | }; 223 | 224 | proto[$ap] = function Concurrently$ap(m) { 225 | if (!isOuter (this)) { 226 | invalidContext (OUTERNAME + '#ap', this, OUTERNAME); 227 | } 228 | 229 | if (!isOuter (m)) { 230 | invalidArgument (OUTERNAME + '#ap', 0, 'be a ' + OUTERNAME, m); 231 | } 232 | 233 | return new Concurrently (ap (this.sequential, m.sequential)); 234 | }; 235 | 236 | proto[$alt] = function Concurrently$alt(m) { 237 | if (!isOuter (this)) { 238 | invalidContext (OUTERNAME + '#alt', this, OUTERNAME); 239 | } 240 | 241 | if (!isOuter (m)) { 242 | invalidArgument (OUTERNAME + '#alt', 0, 'be a ' + OUTERNAME, m); 243 | } 244 | 245 | return new Concurrently (alt (this.sequential, m.sequential)); 246 | }; 247 | 248 | proto[$$show] = function Concurrently$show() { 249 | return OUTERNAME + '(' + show (this.sequential) + ')'; 250 | }; 251 | 252 | proto.toString = function Concurrently$toString() { 253 | if (!isOuter (this)) { 254 | invalidContext (OUTERNAME + '#toString', this, OUTERNAME); 255 | } 256 | return this[$$show] (); 257 | }; 258 | 259 | return construct; 260 | 261 | }; 262 | 263 | })); 264 | 265 | //. [FL3]: https://github.com/fantasyland/fantasy-land/ 266 | //. [FL:Monad]: https://github.com/fantasyland/fantasy-land/#monad 267 | //. [FL:Alternative]: https://github.com/fantasyland/fantasy-land/#alternative 268 | //. [STI]: https://github.com/sanctuary-js/sanctuary-type-identifiers 269 | //. [SS]: https://github.com/sanctuary-js/sanctuary-show 270 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "concurrify", 3 | "version": "2.0.0", 4 | "description": "Turn non-concurrent FantasyLand Applicatives concurrent", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/fluture-js/concurrify.git" 9 | }, 10 | "scripts": { 11 | "doctest": "sanctuary-doctest", 12 | "lint": "sanctuary-lint", 13 | "release": "sanctuary-release", 14 | "test": "npm run lint && sanctuary-test && npm run doctest" 15 | }, 16 | "author": "Aldwin Vlasblom (https://github.com/Avaq)", 17 | "homepage": "https://github.com/fluture-js/concurrify", 18 | "bugs": { 19 | "url": "https://github.com/fluture-js/concurrify/issues" 20 | }, 21 | "license": "MIT", 22 | "engines": { 23 | "node": ">=6.0.0" 24 | }, 25 | "keywords": [ 26 | "algebraic", 27 | "async", 28 | "asynchronous", 29 | "browser", 30 | "control-flow", 31 | "fantasy-land", 32 | "fp", 33 | "functional", 34 | "functor", 35 | "concurrent", 36 | "library", 37 | "monad", 38 | "monadic", 39 | "node", 40 | "parallel" 41 | ], 42 | "files": [ 43 | "/index.js", 44 | "/LICENSE", 45 | "/package.json", 46 | "/README.md" 47 | ], 48 | "dependencies": { 49 | "sanctuary-show": "^1.0.0", 50 | "sanctuary-type-identifiers": "^2.0.0" 51 | }, 52 | "devDependencies": { 53 | "chai": "^4.1.2", 54 | "codecov": "^3.0.0", 55 | "fantasy-land": "^4.0.1", 56 | "sanctuary-scripts": "^3.0.0", 57 | "sanctuary-type-classes": "^12.0.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true, 5 | "browser": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/concurrify.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var expect = require ('chai').expect; 4 | var FL = require ('fantasy-land'); 5 | var show = require ('sanctuary-show'); 6 | var Z = require ('sanctuary-type-classes'); 7 | var type = require ('sanctuary-type-identifiers'); 8 | var concurrify = require ('../'); 9 | 10 | var $$type = '@@type'; 11 | 12 | function Identity(x) { 13 | var id = {x: x, constructor: Identity}; 14 | id[FL.ap] = function(mf) { return Identity (mf.x (x)); }; 15 | id[FL.map] = function(f) { return Identity (f (x)); }; 16 | return id; 17 | } 18 | 19 | Identity[FL.of] = Identity; 20 | Identity[$$type] = 'my/Identity@1'; 21 | 22 | var mockZero = Identity ('zero'); 23 | 24 | function mockAlt(a, b) { return b; } 25 | 26 | function mockAp(mx, mf) { return mx[FL.ap] (mf); } 27 | 28 | describe ('concurrify', function() { 29 | 30 | function noop() {} 31 | 32 | it ('throws when the first argument is not an Applicative Repr', function() { 33 | ['', {}, noop, String, Boolean].forEach (function(x) { 34 | function f() { concurrify (x, mockZero, mockAlt, mockAp); } 35 | expect (f).to.throw (TypeError, /represent an Applicative/); 36 | }); 37 | }); 38 | 39 | it ('throws when the second argument is not represented by the first', function() { 40 | ['', {}, null, 0].forEach (function(x) { 41 | function f() { concurrify (Identity, x, mockAlt, mockAp); } 42 | expect (f).to.throw (TypeError, /Identity/); 43 | }); 44 | }); 45 | 46 | it ('throws when the third argument is not a function', function() { 47 | ['', {}, null, 0].forEach (function(x) { 48 | function f() { concurrify (Identity, mockZero, x, mockAp); } 49 | expect (f).to.throw (TypeError, /be a function/); 50 | }); 51 | }); 52 | 53 | it ('throws when the third argument is not binary', function() { 54 | [noop, function(a) { return a; }].forEach (function(x) { 55 | function f() { concurrify (Identity, mockZero, x, mockAp); } 56 | expect (f).to.throw (TypeError, /be binary/); 57 | }); 58 | }); 59 | 60 | it ('throws when the fourth argument is not a function', function() { 61 | ['', {}, null, 0].forEach (function(x) { 62 | function f() { concurrify (Identity, mockZero, mockAlt, x); } 63 | expect (f).to.throw (TypeError, /be a function/); 64 | }); 65 | }); 66 | 67 | it ('throws when the fourth argument is not binary', function() { 68 | [noop, function(a) { return a; }].forEach (function(x) { 69 | function f() { concurrify (Identity, mockZero, mockAlt, x); } 70 | expect (f).to.throw (TypeError, /be binary/); 71 | }); 72 | }); 73 | 74 | it ('returns a new TypeRepr when given valid input', function() { 75 | var actual = concurrify (Identity, mockZero, mockAlt, mockAp); 76 | expect (actual).to.be.a ('function'); 77 | expect (actual).to.have.property ($$type); 78 | expect (actual).to.have.property (FL.of); 79 | }); 80 | 81 | describe ('TypeRepr', function() { 82 | 83 | var ConcurrentIdentity = concurrify (Identity, mockZero, mockAlt, mockAp); 84 | 85 | it ('throws when the first argument is not represented by Identity', function() { 86 | ['', {}, noop, String, Boolean].forEach (function(x) { 87 | function f() { ConcurrentIdentity (x); } 88 | expect (f).to.throw (TypeError); 89 | }); 90 | }); 91 | 92 | it ('creates Alternatives which are instances of itself', function() { 93 | var actual = ConcurrentIdentity (Z.of (Identity, 1)); 94 | expect (actual).to.satisfy (Z.Alternative.test); 95 | expect (actual).to.be.an.instanceof (ConcurrentIdentity); 96 | }); 97 | 98 | it ('reports being a ConcurrentIdentity from the same vendor and vendor', function() { 99 | var m = ConcurrentIdentity (Z.of (Identity, 1)); 100 | expect (type (m)).to.equal ('my/ConcurrentIdentity@1'); 101 | }); 102 | 103 | describe ('.' + FL.of, function() { 104 | 105 | it ('creates a ConcurrentIdentity of an Identity of the input', function() { 106 | var actual = ConcurrentIdentity[FL.of] ('hello'); 107 | expect (actual).to.be.an.instanceof (ConcurrentIdentity); 108 | expect (actual.sequential.constructor).to.equal (Identity); 109 | expect (actual.sequential.x).to.equal ('hello'); 110 | }); 111 | 112 | }); 113 | 114 | describe ('.' + FL.zero, function() { 115 | 116 | it ('creates a ConcurrentIdentity of the return value of zero', function() { 117 | var actual = ConcurrentIdentity[FL.zero] (); 118 | expect (actual).to.be.an.instanceof (ConcurrentIdentity); 119 | expect (actual.sequential.constructor).to.equal (Identity); 120 | expect (actual.sequential.x).to.equal ('zero'); 121 | }); 122 | 123 | }); 124 | 125 | describe ('#' + FL.map, function() { 126 | 127 | it ('throws when invoked out of context', function() { 128 | var m = ConcurrentIdentity[FL.of] (1); 129 | ['', {}, noop, String, Boolean].forEach (function(x) { 130 | function f() { m[FL.map].call (x); } 131 | expect (f).to.throw (TypeError, /context/); 132 | }); 133 | }); 134 | 135 | it ('throws when called without a function', function() { 136 | var m = ConcurrentIdentity[FL.of] (1); 137 | ['', {}, null, 0].forEach (function(x) { 138 | function f() { m[FL.map] (x); } 139 | expect (f).to.throw (TypeError, /be a function/); 140 | }); 141 | }); 142 | 143 | it ('delegates to the inner map', function(done) { 144 | var id = Identity (1); 145 | 146 | id[FL.map] = function(f) { 147 | expect (f).to.equal (noop); 148 | expect (this).to.equal (id); 149 | done (); 150 | }; 151 | 152 | var cid = ConcurrentIdentity (id); 153 | cid[FL.map] (noop); 154 | }); 155 | 156 | it ('behaves like map', function() { 157 | var m = ConcurrentIdentity[FL.of] (1); 158 | var m1 = m[FL.map] (function(x) { return x + 1; }); 159 | expect (m1.sequential.x).to.equal (2); 160 | }); 161 | 162 | }); 163 | 164 | describe ('#' + FL.ap, function() { 165 | 166 | it ('throws when invoked out of context', function() { 167 | var m = ConcurrentIdentity[FL.of] (1); 168 | ['', {}, noop, String, Boolean].forEach (function(x) { 169 | function f() { m[FL.ap].call (x); } 170 | expect (f).to.throw (TypeError, /context/); 171 | }); 172 | }); 173 | 174 | it ('throws when called without a ConcurrentIdentity', function() { 175 | var m = ConcurrentIdentity[FL.of] (1); 176 | ['', {}, null, 0, noop].forEach (function(x) { 177 | function f() { m[FL.ap] (x); } 178 | expect (f).to.throw (TypeError, /ConcurrentIdentity/); 179 | }); 180 | }); 181 | 182 | it ('delegates to the given ap', function(done) { 183 | var x = 1; 184 | function f(x) { return x; } 185 | var idx = Identity (x); 186 | var idf = Identity (f); 187 | function mockAp(a, b) { 188 | expect (a).to.equal (idx); 189 | expect (b).to.equal (idf); 190 | done (); 191 | } 192 | var ConcurrentIdentity = concurrify (Identity, mockZero, mockAlt, mockAp); 193 | var cidx = ConcurrentIdentity (idx); 194 | var cidf = ConcurrentIdentity (idf); 195 | cidx[FL.ap] (cidf); 196 | }); 197 | 198 | }); 199 | 200 | describe ('#' + FL.alt, function() { 201 | 202 | it ('throws when invoked out of context', function() { 203 | var m = ConcurrentIdentity[FL.of] (1); 204 | ['', {}, noop, String, Boolean].forEach (function(x) { 205 | function f() { m[FL.alt].call (x); } 206 | expect (f).to.throw (TypeError, /context/); 207 | }); 208 | }); 209 | 210 | it ('throws when called without a ConcurrentIdentity', function() { 211 | var m = ConcurrentIdentity[FL.of] (1); 212 | ['', {}, null, 0, noop].forEach (function(x) { 213 | function f() { m[FL.alt] (x); } 214 | expect (f).to.throw (TypeError, /ConcurrentIdentity/); 215 | }); 216 | }); 217 | 218 | it ('delegates to the given alt', function(done) { 219 | var x = 1; 220 | function f(x) { return x; } 221 | var idx = Identity (x); 222 | var idf = Identity (f); 223 | function mockAlt(a, b) { 224 | expect (a).to.equal (idx); 225 | expect (b).to.equal (idf); 226 | done (); 227 | } 228 | var ConcurrentIdentity = concurrify (Identity, mockZero, mockAlt, mockAp); 229 | var cidx = ConcurrentIdentity (idx); 230 | var cidf = ConcurrentIdentity (idf); 231 | cidx[FL.alt] (cidf); 232 | }); 233 | 234 | }); 235 | 236 | describe ('#@@show', function() { 237 | 238 | var inner = Z.of (Identity, 1); 239 | var m = ConcurrentIdentity (inner); 240 | 241 | it ('returns a string representation of the data-structure', function() { 242 | expect (show (m)).to.equal ('ConcurrentIdentity(' + show (inner) + ')'); 243 | }); 244 | 245 | }); 246 | 247 | describe ('#toString', function() { 248 | 249 | var inner = Z.of (Identity, 1); 250 | var m = ConcurrentIdentity (inner); 251 | 252 | it ('throws when invoked out of context', function() { 253 | ['', {}, noop, String, Boolean].forEach (function(x) { 254 | function f() { m.toString.call (x); } 255 | expect (f).to.throw (TypeError, /context/); 256 | }); 257 | }); 258 | 259 | it ('returns a string representation of the data-structure', function() { 260 | expect (m.toString ()).to.equal (show (m)); 261 | }); 262 | 263 | }); 264 | 265 | }); 266 | 267 | }); 268 | --------------------------------------------------------------------------------