concurrify
Advanced tools
Comparing version 0.0.1 to 0.1.0
141
index.js
@@ -7,13 +7,146 @@ (function(global, f){ | ||
if(module && typeof module.exports !== 'undefined'){ | ||
module.exports = f(); | ||
module.exports = f(require('sanctuary-type-classes'), require('sanctuary-type-identifiers')); | ||
}else{ | ||
global.concurrify = f(); | ||
global.concurrify = f(global.sanctuaryTypeClasses, global.sanctuaryTypeIdentifiers); | ||
} | ||
}(/*istanbul ignore next*/(global || window || this), function(){ | ||
}(/*istanbul ignore next*/(global || window || this), function(Z, type){ | ||
'use strict'; | ||
return function(){}; | ||
var $alt = 'fantasy-land/alt'; | ||
var $ap = 'fantasy-land/ap'; | ||
var $map = 'fantasy-land/map'; | ||
var $of = 'fantasy-land/of'; | ||
var $zero = 'fantasy-land/zero'; | ||
var $$type = '@@type'; | ||
var ordinal = ['first', 'second', 'third', 'fourth', 'fifth']; | ||
function isFunction(f){ | ||
return typeof f === 'function'; | ||
} | ||
function isBinary(f){ | ||
return f.length >= 2; | ||
} | ||
function isApplicativeRepr(Repr){ | ||
try{ | ||
return Z.Applicative.test(Z.of(Repr)); | ||
}catch(_){ | ||
return false; | ||
} | ||
} | ||
function invalidArgument(it, at, expected, actual){ | ||
throw new TypeError( | ||
it | ||
+ ' expects its ' | ||
+ ordinal[at] | ||
+ ' argument to ' | ||
+ expected | ||
+ '\n Actual: ' | ||
+ Z.toString(actual) | ||
); | ||
} | ||
function invalidContext(it, actual, an){ | ||
throw new TypeError( | ||
it + ' was invoked outside the context of a ' + an + '. \n Called on: ' + Z.toString(actual) | ||
); | ||
} | ||
function last(xs){ | ||
return xs[xs.length - 1]; | ||
} | ||
function getReprType(Repr){ | ||
return Repr[$$type] || Repr.name || 'Anonymous'; | ||
} | ||
function concurrentPrepender(x, i, xs){ | ||
return i === xs.length - 1 ? ('Concurrent' + x) : x; | ||
} | ||
function computeType(type){ | ||
return type.split('/').map(concurrentPrepender).join('/'); | ||
} | ||
//concurrify :: Applicative m | ||
// => (TypeRep m, m a, (m a, m a) -> m a, (m a, m (a -> b)) -> m b) | ||
// -> Concurrently m | ||
return function concurrify(Repr, zero, alt, ap){ | ||
var INNERTYPE = getReprType(Repr); | ||
var OUTERTYPE = computeType(INNERTYPE); | ||
function Concurrently(sequential){ | ||
this.sequential = sequential; | ||
} | ||
function isInner(x){ | ||
return x instanceof Repr | ||
|| (Boolean(x) && x.constructor === Repr) | ||
|| type(x) === Repr[$$type]; | ||
} | ||
function isOuter(x){ | ||
return x instanceof Concurrently | ||
|| (Boolean(x) && x.constructor === Concurrently) | ||
|| type(x) === OUTERTYPE; | ||
} | ||
function construct(x){ | ||
if(!isInner(x)) invalidArgument(OUTERTYPE, 0, 'be of type "' + INNERTYPE + '"', x); | ||
return new Concurrently(x); | ||
} | ||
if(!isApplicativeRepr(Repr)) invalidArgument('concurrify', 0, 'represent an Applicative', Repr); | ||
if(!isInner(zero)) invalidArgument('concurrify', 1, 'be of type "' + INNERTYPE + '"', zero); | ||
if(!isFunction(alt)) invalidArgument('concurrify', 2, 'be a function', alt); | ||
if(!isBinary(alt)) invalidArgument('concurrify', 2, 'be binary', alt); | ||
if(!isFunction(ap)) invalidArgument('concurrify', 3, 'be a function', ap); | ||
if(!isBinary(ap)) invalidArgument('concurrify', 3, 'be binary', ap); | ||
var proto = Concurrently.prototype = construct.prototype = {constructor: construct}; | ||
construct[$$type] = OUTERTYPE; | ||
var mzero = new Concurrently(zero); | ||
construct[$zero] = function Concurrently$zero(){ | ||
return mzero; | ||
}; | ||
construct[$of] = function Concurrently$of(value){ | ||
return new Concurrently(Z.of(Repr, value)); | ||
}; | ||
proto[$map] = function Concurrently$map(mapper){ | ||
if(!isOuter(this)) invalidContext(OUTERTYPE + '#map', this, OUTERTYPE); | ||
if(!isFunction(mapper)) invalidArgument(OUTERTYPE + '#map', 0, 'be a function', mapper); | ||
return new Concurrently(Z.map(mapper, this.sequential)); | ||
}; | ||
proto[$ap] = function Concurrently$ap(m){ | ||
if(!isOuter(this)) invalidContext(OUTERTYPE + '#ap', this, OUTERTYPE); | ||
if(!isOuter(m)) invalidArgument(OUTERTYPE + '#ap', 0, 'be a Concurrently', m); | ||
return new Concurrently(ap(this.sequential, m.sequential)); | ||
}; | ||
proto[$alt] = function Concurrently$alt(m){ | ||
if(!isOuter(this)) invalidContext(OUTERTYPE + '#alt', this, OUTERTYPE); | ||
if(!isOuter(m)) invalidArgument(OUTERTYPE + '#alt', 0, 'be a Concurrently', m); | ||
return new Concurrently(alt(this.sequential, m.sequential)); | ||
}; | ||
proto.toString = function Concurrently$toString(){ | ||
if(!isOuter(this)) invalidContext(OUTERTYPE + '#toString', this, OUTERTYPE); | ||
return last(OUTERTYPE.split('/')) + '(' + Z.toString(this.sequential) + ')'; | ||
}; | ||
return construct; | ||
}; | ||
})); |
{ | ||
"name": "concurrify", | ||
"version": "0.0.1", | ||
"version": "0.1.0", | ||
"description": "Turn non-concurrent FantasyLand Applicatives concurrent", | ||
@@ -10,8 +10,6 @@ "main": "index.js", | ||
"lint": "eslint index.js test", | ||
"lint:fix": "eslint --fix index.js test", | ||
"lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md", | ||
"lint:fix": "npm run lint -- --fix", | ||
"release": "npm outdated --long && xyz --edit --repo git@github.com:fluture-js/concurrify.git --tag 'X.Y.Z' --increment", | ||
"toc": "node scripts/toc.js", | ||
"test": "npm run test:all && npm run test:coverage && codecov", | ||
"test:all": "npm run lint && npm run lint:readme && npm run test:unit", | ||
"test:all": "npm run lint && npm run test:unit", | ||
"test:unit": "_mocha --ui bdd --reporter list --check-leaks --full-trace test/**.test.js", | ||
@@ -46,4 +44,8 @@ "test:coverage": "npm run clean && istanbul cover --report html _mocha -- --ui bdd --reporter dot --check-leaks --bail test/**.test.js" | ||
], | ||
"dependencies": {}, | ||
"dependencies": { | ||
"sanctuary-type-classes": "^3.0.0", | ||
"sanctuary-type-identifiers": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"chai": "^3.5.0", | ||
"codecov": "^1.0.1", | ||
@@ -54,6 +56,3 @@ "eslint": "^3.0.1", | ||
"istanbul": "^0.4.2", | ||
"markdown-toc": "^1.0.2", | ||
"mocha": "^3.0.2", | ||
"remark-cli": "^2.1.0", | ||
"remark-validate-links": "^5.0.0", | ||
"rimraf": "^2.4.3", | ||
@@ -60,0 +59,0 @@ "xyz": "^2.0.1" |
@@ -9,2 +9,53 @@ # Concurrify | ||
Does nothing yet. | ||
Turn non-concurrent [FantasyLand 3][FL3] Applicatives concurrent. | ||
Most time-dependent applicatives are very useful as Monads, because it gives | ||
them the ability to run sequentially, where each step depends on the previous. | ||
However, they lose the ability to run concurrently. This library allows one to | ||
wrap a [`Monad`][FL:Monad] (with sequential `ap`) in an | ||
[`Alternative`][FL:Alternative] (with parallel `ap`). | ||
## Usage | ||
```js | ||
//The concurrify function takes four arguments, explained below. | ||
const concurrify = require('concurrify'); | ||
//We load the Type Representative of the Applicative we want to transform. | ||
const Future = require('fluture'); | ||
//We create a "zero" instance and an "alt" function for "Alternative". | ||
const zero = Future(() => {}); | ||
const alt = Future.race; | ||
//We create an override "ap" function that runs the Applicatives concurrently. | ||
const ap = (mx, mf) => Future.both(mx, mf).map(([x, f]) => f(x)); | ||
//Calling concurrify with these arguments gives us a new Type Representative. | ||
const ConcurrentFuture = concurrify(Future, zero, alt, ap); | ||
//We can use our type as such: | ||
ConcurrentFuture(Future.of(1)).sequential.fork(console.error, console.log); | ||
``` | ||
## API | ||
```hs | ||
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 | ||
``` | ||
## Interoperability | ||
* Implements [FantasyLand 3][FL3] `Alternative` (`of`, `zero`, `map`, `ap`, `alt`). | ||
* Instances can be identified by, and are compared using, [Sanctuary Type Identifiers][STI]. | ||
<!-- References --> | ||
[FL3]: https://github.com/fantasyland/fantasy-land/ | ||
[FL:Monad]: https://github.com/fantasyland/fantasy-land/#monad | ||
[FL:Alternative]: https://github.com/fantasyland/fantasy-land/#alternative | ||
[STI]: https://github.com/sanctuary-js/sanctuary-type-identifiers |
'use strict'; | ||
var concurrify = require('../'); | ||
var expect = require('chai').expect; | ||
var Z = require('sanctuary-type-classes'); | ||
var type = require('sanctuary-type-identifiers'); | ||
var FL = require('fantasy-land'); | ||
var $$type = '@@type'; | ||
var Identity = function(x){ | ||
var id = {x: x, constructor: Identity}; | ||
id[FL.ap] = function(mf){ return Identity(mf.x(x)) }; | ||
id[FL.map] = function(f){ return Identity(f(x)) }; | ||
return id; | ||
}; | ||
Identity[FL.of] = Identity; | ||
Identity[$$type] = 'my/Identity'; | ||
var mockZero = Identity('zero'); | ||
function mockAlt(a, b){ return b } | ||
function mockAp(mx, mf){ return mx[FL.ap](mf) } | ||
describe('concurrify', function(){ | ||
it('does nothing yet', function(){ | ||
concurrify(); | ||
var noop = function(){}; | ||
it('throws when the first argument is not an Applicative Repr', function(){ | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ concurrify(x, mockZero, mockAlt, mockAp) }; | ||
expect(f).to.throw(TypeError, /represent an Applicative/); | ||
}); | ||
}); | ||
it('throws when the second argument is not represented by the first', function(){ | ||
['', {}, null, 0].forEach(function(x){ | ||
var f = function(){ concurrify(Identity, x, mockAlt, mockAp) }; | ||
expect(f).to.throw(TypeError, /Identity/); | ||
}); | ||
}); | ||
it('throws when the third argument is not a function', function(){ | ||
['', {}, null, 0].forEach(function(x){ | ||
var f = function(){ concurrify(Identity, mockZero, x, mockAp) }; | ||
expect(f).to.throw(TypeError, /be a function/); | ||
}); | ||
}); | ||
it('throws when the third argument is not binary', function(){ | ||
[noop, function(a){return a}].forEach(function(x){ | ||
var f = function(){ concurrify(Identity, mockZero, x, mockAp) }; | ||
expect(f).to.throw(TypeError, /be binary/); | ||
}); | ||
}); | ||
it('throws when the fourth argument is not a function', function(){ | ||
['', {}, null, 0].forEach(function(x){ | ||
var f = function(){ concurrify(Identity, mockZero, mockAlt, x) }; | ||
expect(f).to.throw(TypeError, /be a function/); | ||
}); | ||
}); | ||
it('throws when the fourth argument is not binary', function(){ | ||
[noop, function(a){return a}].forEach(function(x){ | ||
var f = function(){ concurrify(Identity, mockZero, mockAlt, x) }; | ||
expect(f).to.throw(TypeError, /be binary/); | ||
}); | ||
}); | ||
it('returns a new TypeRepr when given valid input', function(){ | ||
var actual = concurrify(Identity, mockZero, mockAlt, mockAp); | ||
expect(actual).to.be.a('function'); | ||
expect(actual).to.have.property($$type); | ||
expect(actual).to.have.property(FL.of); | ||
}); | ||
describe('TypeRepr', function(){ | ||
var ConcurrentIdentity = concurrify(Identity, mockZero, mockAlt, mockAp); | ||
it('throws when the first argument is not represented by Identity', function(){ | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ ConcurrentIdentity(x) }; | ||
expect(f).to.throw(TypeError); | ||
}); | ||
}); | ||
it('creates Alternatives which are instances of itself', function(){ | ||
var actual = ConcurrentIdentity(Z.of(Identity, 1)); | ||
expect(actual).to.satisfy(Z.Alternative.test); | ||
expect(actual).to.be.an.instanceof(ConcurrentIdentity); | ||
}); | ||
it('reports being a ConcurrentIdentity from the same vendor', function(){ | ||
var m = ConcurrentIdentity(Z.of(Identity, 1)); | ||
expect(type(m)).to.equal('my/ConcurrentIdentity'); | ||
}); | ||
describe('.' + FL.of, function(){ | ||
it('creates a ConcurrentIdentity of an Identity of the input', function(){ | ||
var actual = ConcurrentIdentity[FL.of]('hello'); | ||
expect(actual).to.be.an.instanceof(ConcurrentIdentity); | ||
expect(actual.sequential.constructor).to.equal(Identity); | ||
expect(actual.sequential.x).to.equal('hello'); | ||
}); | ||
}); | ||
describe('.' + FL.zero, function(){ | ||
it('creates a ConcurrentIdentity of the return value of zero', function(){ | ||
var actual = ConcurrentIdentity[FL.zero](); | ||
expect(actual).to.be.an.instanceof(ConcurrentIdentity); | ||
expect(actual.sequential.constructor).to.equal(Identity); | ||
expect(actual.sequential.x).to.equal('zero'); | ||
}); | ||
}); | ||
describe('#' + FL.map, function(){ | ||
it('throws when invoked out of context', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ m[FL.map].call(x) }; | ||
expect(f).to.throw(TypeError, /context/); | ||
}); | ||
}); | ||
it('throws when called without a function', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, null, 0].forEach(function(x){ | ||
var f = function(){ m[FL.map](x) }; | ||
expect(f).to.throw(TypeError, /be a function/); | ||
}); | ||
}); | ||
it('delegates to the inner map', function(done){ | ||
var mapper = function(){}; | ||
var id = Identity(1); | ||
id[FL.map] = function(f){ | ||
expect(f).to.equal(mapper); | ||
expect(this).to.equal(id); | ||
done(); | ||
}; | ||
var cid = ConcurrentIdentity(id); | ||
cid[FL.map](mapper); | ||
}); | ||
it('behaves like map', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
var m1 = m[FL.map](function(x){ return x + 1 }); | ||
expect(m1.sequential.x).to.equal(2); | ||
}); | ||
}); | ||
describe('#' + FL.ap, function(){ | ||
it('throws when invoked out of context', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ m[FL.ap].call(x) }; | ||
expect(f).to.throw(TypeError, /context/); | ||
}); | ||
}); | ||
it('throws when called without a ConcurrentIdentity', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, null, 0, noop].forEach(function(x){ | ||
var f = function(){ m[FL.ap](x) }; | ||
expect(f).to.throw(TypeError, /ConcurrentIdentity/); | ||
}); | ||
}); | ||
it('delegates to the given ap', function(done){ | ||
var x = 1; | ||
var f = function(x){return x}; | ||
var idx = Identity(x); | ||
var idf = Identity(f); | ||
var mockAp = function(a, b){ | ||
expect(a).to.equal(idx); | ||
expect(b).to.equal(idf); | ||
done(); | ||
}; | ||
var ConcurrentIdentity = concurrify(Identity, mockZero, mockAlt, mockAp); | ||
var cidx = ConcurrentIdentity(idx); | ||
var cidf = ConcurrentIdentity(idf); | ||
cidx[FL.ap](cidf); | ||
}); | ||
}); | ||
describe('#' + FL.alt, function(){ | ||
it('throws when invoked out of context', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ m[FL.alt].call(x) }; | ||
expect(f).to.throw(TypeError, /context/); | ||
}); | ||
}); | ||
it('throws when called without a ConcurrentIdentity', function(){ | ||
var m = ConcurrentIdentity[FL.of](1); | ||
['', {}, null, 0, noop].forEach(function(x){ | ||
var f = function(){ m[FL.alt](x) }; | ||
expect(f).to.throw(TypeError, /ConcurrentIdentity/); | ||
}); | ||
}); | ||
it('delegates to the given alt', function(done){ | ||
var x = 1; | ||
var f = function(x){return x}; | ||
var idx = Identity(x); | ||
var idf = Identity(f); | ||
var mockAlt = function(a, b){ | ||
expect(a).to.equal(idx); | ||
expect(b).to.equal(idf); | ||
done(); | ||
}; | ||
var ConcurrentIdentity = concurrify(Identity, mockZero, mockAlt, mockAp); | ||
var cidx = ConcurrentIdentity(idx); | ||
var cidf = ConcurrentIdentity(idf); | ||
cidx[FL.alt](cidf); | ||
}); | ||
}); | ||
describe('#toString', function(){ | ||
var inner = Z.of(Identity, 1); | ||
var m = ConcurrentIdentity(inner); | ||
it('throws when invoked out of context', function(){ | ||
['', {}, noop, String, Boolean].forEach(function(x){ | ||
var f = function(){ m.toString.call(x) }; | ||
expect(f).to.throw(TypeError, /context/); | ||
}); | ||
}); | ||
it('returns a string representation of the data-structure', function(){ | ||
expect(m.toString()).to.equal('ConcurrentIdentity(' + Z.toString(inner) + ')'); | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
19883
9
350
61
1
2
12
1
+ Addedsanctuary-type-classes@3.1.0(transitive)
+ Addedsanctuary-type-identifiers@1.0.0(transitive)