Skip to content

Commit

Permalink
Add ParallelFuture based on concurrify
Browse files Browse the repository at this point in the history
Adds the following functions:

* Future.Par :: Future a b -> ConcurrentFuture a b
* Future.seq :: ConcurrentFuture a b -> Future a b
* Future.never :: () -> Future a a

Closes #44
  • Loading branch information
Avaq committed Feb 14, 2017
1 parent 8b63433 commit 1c50bc2
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 2 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,42 @@ Future.parallel(Infinity, stabalizedFutures).fork(console.error, console.log);
//> [ Right(0), Left("failed"), Right(2), Right(3) ]
```

#### ConcurrentFuture
##### `.Par :: Future a b -> ParallelFuture a b`
##### `.seq :: ParallelFuture a b -> Future a b`

ConcurrentFuture (or `Par` for short) is the result of applying
[`concurrify`][concurrify] to `Future`. It provides a mechanism for constructing
a [FantasyLand `Alternative`][FL:alternative] from a member of `Future`. This
allows Futures to benefit from the Alternative Interface, which includes
parallel `ap`, `zero` and `alt`.

The idea is that you can switch back and forth between `Future` and
`ConcurrentFuture`, using `Par` and `seq`, to get sequential or concurrent
behaviour respectively.

```js
const {of, ap, zero, alt, sequence} = require('sanctuary');
const {Future, Par, seq} = require('fluture');

//Some dummy values
const x = 1;
const f = a => a + 1;

//The following two are equal ways to construct a ConcurrentFuture
const parx = of(Par, x);
const parf = Par(of(Future, f));

//We can make use of parallel apply
seq(ap(parx, parf)).value(console.log) //> 2

//Or concurrent sequencing
seq(sequence(Par, [parx, parf])).value(console.log) //> [x, f]

//Or racing with alternative
seq(alt(zero(Par), parx)).value(console.log) //> 1
```

### Utility functions

#### isFuture
Expand Down Expand Up @@ -917,6 +953,7 @@ Credits for the logo go to [Erik Fuente][8].
[FL1]: https://github.com/fantasyland/fantasy-land/tree/v1.0.1
[FL2]: https://github.com/fantasyland/fantasy-land/tree/v2.2.0
[FL3]: https://github.com/fantasyland/fantasy-land
[FL:alternative]: https://github.com/fantasyland/fantasy-land#alternative
[FL:functor]: https://github.com/fantasyland/fantasy-land#functor
[FL:chain]: https://github.com/fantasyland/fantasy-land#chain
[FL:apply]: https://github.com/fantasyland/fantasy-land#apply
Expand All @@ -937,6 +974,8 @@ Credits for the logo go to [Erik Fuente][8].
[$]: https://github.com/sanctuary-js/sanctuary-def
[$:BinaryType]: https://github.com/sanctuary-js/sanctuary-def#BinaryType

[concurrify]: https://github.com/fluture-js/concurrify

[1]: https://github.com/futurize/futurize
[2]: https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch7.html
[3]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#iterator
Expand Down
83 changes: 81 additions & 2 deletions fluture.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
/*istanbul ignore next*/
if(module && typeof module.exports !== 'undefined'){
module.exports = f(
require('concurrify'),
require('inspect-f'),
require('sanctuary-type-classes'),
require('sanctuary-type-identifiers')
);
}else{
global.Fluture = f(
global.concurrify,
global.inspectf,
global.sanctuaryTypeClasses,
global.sanctuaryTypeIdentifiers
);
}

}(/*istanbul ignore next*/(global || window || this), function(inspectf, Z, type){
}(/*istanbul ignore next*/(global || window || this), function(concurrify, inspectf, Z, type){

'use strict';

Expand Down Expand Up @@ -623,6 +625,10 @@
return hook$acquire(acquire, cleanup, consume);
};

Future.never = function Future$never(){
return new FutureNever;
};

//Utilities.
Future.util = {
Next,
Expand Down Expand Up @@ -1342,6 +1348,45 @@

//----------

function FutureParallelAp(mval, mfunc){
this._mval = mval;
this._mfunc = mfunc;
}

FutureParallelAp.prototype = Object.create(Future.prototype);

FutureParallelAp.prototype._f = function FutureParallelAp$fork(rej, res){
let func, val, okval = false, okfunc = false, rejected = false, c1, c2;
function FutureParallelAp$rej(x){
if(!rejected){
rejected = true;
rej(x);
}
}
c1 = this._mval._f(FutureParallelAp$rej, function FutureParallelAp$fork$resVal(x){
c1 = noop;
if(!okval) return void (okfunc = true, val = x);
check$ap$f(func);
res(func(x));
});
c2 = this._mfunc._f(FutureParallelAp$rej, function FutureParallelAp$fork$resFunc(f){
c2 = noop;
if(!okfunc) return void (okval = true, func = f);
check$ap$f(f);
res(f(val));
});
return function FutureParallelAp$fork$cancel(){
c1();
c2();
};
};

FutureParallelAp.prototype.toString = function FutureParallelAp$toString(){
return `new FutureParallelAp(${this._mval.toString()}, ${this._mfunc.toString()})`;
};

//----------

function FutureSwap(parent){
this._parent = parent;
}
Expand Down Expand Up @@ -1565,6 +1610,20 @@
return `${this._left.toString()}.finally(${this._right.toString()})`;
};

//----------

function FutureNever(){}

FutureNever.prototype = Object.create(Future.prototype);

FutureNever.prototype._f = function FutureNever$fork(){
return noop;
};

FutureNever.prototype.toString = function FutureNever$toString(){
return `Future.never()`;
};

Future.classes = {
SafeFuture,
ChainRec,
Expand All @@ -1585,14 +1644,34 @@
FutureMapRej,
FutureBimap,
FutureAp,
FutureParallelAp,
FutureSwap,
FutureRace,
FutureAnd,
FutureOr,
FutureBoth,
FutureFold,
FutureHook,
FutureFinally
FutureFinally,
FutureNever
};

//////////////
// Parallel //
//////////////

const ParallelFuture = concurrify(Future, Future.never(), Future.race, function(mval, mfunc){
return new FutureParallelAp(mval, mfunc);
});

function isParallel(x){
return x instanceof ParallelFuture || type(x) === ParallelFuture['@@type'];
}

Future.Par = ParallelFuture;
Future.seq = function seq(par){
if(!isParallel(par)) invalidArgument('Future.seq', 0, 'to be a Par', par);
return par.sequential;
};

return Future;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"sequential"
],
"dependencies": {
"concurrify": "^0.1.0",
"inspect-f": "^1.2.0",
"sanctuary-type-classes": "^3.0.0",
"sanctuary-type-identifiers": "^1.0.0"
Expand Down
15 changes: 15 additions & 0 deletions test/1.future.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,21 @@ describe('Future', () => {

});

describe('.seq()', () => {

it('throws when not given a Parallel', () => {
const f = () => Future.seq(1);
expect(f).to.throw(TypeError, /Future/);
});

it('returns the Future contained in the Parallel', () => {
const par = Future.Par(F.mock);
const seq = Future.seq(par);
expect(seq).to.equal(F.mock);
});

});

describe('.extractLeft()', () => {

it('throws when not given a Future', () => {
Expand Down
37 changes: 37 additions & 0 deletions test/5.future-never.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict';

const expect = require('chai').expect;
const Future = require('../fluture.js');
const U = require('./util');
const type = require('sanctuary-type-identifiers');

describe('FutureNever', () => {

it('extends Future', () => {
expect(Future.never()).to.be.an.instanceof(Future);
});

it('is considered a member of fluture/Fluture', () => {
expect(type(Future.never())).to.equal('fluture/Future');
});

describe('#fork()', () => {

it('does nothing and returns a noop cancel function', () => {
const m = Future.never();
const cancel = m.fork(U.noop, U.noop);
cancel();
});

});

describe('#toString()', () => {

it('returns the code to create the FutureNever', () => {
const m = Future.never();
expect(m.toString()).to.equal('Future.never()');
});

});

});
Loading

0 comments on commit 1c50bc2

Please sign in to comment.