Skip to content

Commit

Permalink
Switch the entire library to a functional-first approach
Browse files Browse the repository at this point in the history
  • Loading branch information
Avaq committed Apr 10, 2019
1 parent c6c2097 commit 575a452
Show file tree
Hide file tree
Showing 161 changed files with 2,465 additions and 4,535 deletions.
34 changes: 0 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1105,16 +1105,6 @@ Future.after(300, null)
//> "hello"
```

With good old `reduce`, we can turn this into an asynchronous `all` function,
where the resulting Future will be the leftmost to reject, or the rightmost to
resolve.

```js
var all = ms => ms.reduce(Future.and, Future.of(0));
all([Future.after(20, 1), Future.of(2)]).value(console.log);
//> 2
```

#### alt

<details><summary><code>alt :: Alt f => f a -> f a -> f a</code></summary>
Expand Down Expand Up @@ -1150,16 +1140,6 @@ Future.rejectAfter(300, new Error('Failed'))
//> "hello"
```

With good old `reduce`, we can turn this into an asynchronous `any` function,
where the resulting Future will be the leftmost to resolve, or the rightmost
to reject.

```js
var any = ms => ms.reduce(Future.alt, Future.reject('empty list'));
any([Future.reject(1), Future.after(20, 2), Future.of(3)]).value(console.log);
//> 2
```

#### finally

<details><summary><code>finally :: Future a c -> Future a b -> Future a b</code></summary>
Expand Down Expand Up @@ -1430,20 +1410,6 @@ Future.after(100, 'hello')
//> "bye"
```

With good old `reduce`, we can turn this into a `first` function, where the
resulting Future will be the first to resolve, or the first to reject.

```js
var first = futures => futures.reduce(Future.race, Future.never);
first([
Future.after(100, 'hello'),
Future.after(50, 'bye'),
Future.rejectAfter(25, 'nope')
])
.fork(console.error, console.log);
//! "nope"
```

#### both

<details><summary><code>both :: Future a b -> Future a c -> Future a (Pair b c)</code></summary>
Expand Down
12 changes: 12 additions & 0 deletions bench/fluture.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ module.exports = require('sanctuary-benchmark')(Old, New, config, {
{}, ({of, map}) => run(map(plus1, of(1)))
],

'run.transform.sync.swap.one': [
{}, ({of, swap}) => run(swap(of(42)))
],

'run.transform.sync.swap.many': [
{}, ({of, swap}) => {
let m = of(1);
for(let i = 0; i < 1000; i++) { m = swap(m); }
run(m);
}
],

'run.transform.sync.chain.one': [
{}, ({of, chain}) => run(chain(compose(of, plus1), of(1)))
],
Expand Down
58 changes: 58 additions & 0 deletions fluenture.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Future.prototype.pipe = function Future$pipe(f){
// if(!isFuture(this)) throw invalidContext('Future#pipe', this);
// if(!isFunction(f)) throw invalidArgument('Future#pipe', 0, 'be a Function', f);
// return f(this);
// };

// Future.prototype.alt = createFlippedUnaryMethod('alt', alt);
// Future.prototype.and = createFlippedUnaryMethod('and', and);
// Future.prototype.ap = createFlippedUnaryMethod('ap', ap);
// Future.prototype.bimap = createBinaryMethod('bimap', bimap);
// Future.prototype.both = createFlippedUnaryMethod('both', both);
// Future.prototype.chain = createUnaryMethod('chain', chain);
// Future.prototype.chainRej = createUnaryMethod('chainRej', chainRej);
// Future.prototype.finally = createFlippedUnaryMethod('finally', lastly);
// Future.prototype.fold = createBinaryMethod('fold', fold);
// Future.prototype.lastly = createFlippedUnaryMethod('lastly', lastly);
// Future.prototype.map = createUnaryMethod('map', map);
// Future.prototype.mapRej = createUnaryMethod('mapRej', mapRej);
// Future.prototype.or = createFlippedUnaryMethod('or', alt);
// Future.prototype.race = createFlippedUnaryMethod('race', race);
// Future.prototype.swap = createFlippedUnaryMethod('swap', swap);
// Future.prototype.done = createUnaryMethod('done', done);

// Future.prototype.fork = function Future$fork(rej, res){
// if(!isFuture(this)) throwInvalidContext('Future#fork', this);
// if(!isFunction(rej)) throwInvalidArgument('Future#fork', 0, 'be a Function', rej);
// if(!isFunction(res)) throwInvalidArgument('Future#fork', 1, 'be a Function', res);
// return this._interpret(raise, rej, res);
// };

// Future.prototype.forkCatch = function Future$forkCatch(rec, rej, res){
// if(!isFuture(this)) throwInvalidContext('Future#forkCatch', this);
// if(!isFunction(rec)) throwInvalidArgument('Future#forkCatch', 0, 'be a Function', rec);
// if(!isFunction(rej)) throwInvalidArgument('Future#forkCatch', 1, 'be a Function', rej);
// if(!isFunction(res)) throwInvalidArgument('Future#forkCatch', 2, 'be a Function', res);
// return this._interpret(rec, rej, res);
// };

// Future.prototype.value = function Future$value(res){
// if(!isFuture(this)) throwInvalidContext('Future#value', this);
// if(!isFunction(res)) throwInvalidArgument('Future#value', 0, 'be a Function', res);
// var _this = this;
// return _this._interpret(raise, function Future$value$rej(x){
// raise(error(
// 'Future#value was called on a rejected Future\n' +
// ' Rejection: ' + show(x) + '\n' +
// ' Future: ' + _this.toString()
// ));
// }, res);
// };

// Future.prototype.promise = function Future$promise(){
// if(!isFuture(this)) throwInvalidContext('Future#promise', this);
// var _this = this;
// return new Promise(function Future$promise$computation(res, rej){
// _this._interpret(raise, rej, res);
// });
// };
122 changes: 87 additions & 35 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -1,54 +1,106 @@
import concurrify from 'concurrify';
import type from 'sanctuary-type-identifiers';
import {throwInvalidArgument} from './src/internal/throw';
import {Future, resolve, reject, never} from './src/future';

import {captureContext} from './src/internal/debug';
import {invalidArgument} from './src/internal/error';
import {nil} from './src/internal/list';
import {FL} from './src/internal/const';

import {Future, never} from './src/future';

import {AltTransformation} from './src/alt';
import {ApTransformation} from './src/ap';
import {BimapTransformation} from './src/bimap';
import {ChainTransformation} from './src/chain';
import {MapTransformation} from './src/map';

import {chainRec} from './src/chain-rec';
import {ap, map, bimap, chain, race, alt} from './src/dispatchers/index';

Future.resolve = Future.of = Future[FL.of] = resolve;
Future.chainRec = Future[FL.chainRec] = chainRec;
Future.reject = reject;
Future.ap = ap;
Future.alt = alt;
Future.map = map;
Future.bimap = bimap;
Future.chain = chain;

var Par = concurrify(Future, never, race, function parallelAp(a, b){ return b._parallelAp(a) });
Par.of = Par[FL.of];
Par.zero = Par[FL.zero];
Par.map = map;
Par.ap = ap;
Par.alt = alt;

function isParallel(x){
import {parallelAp} from './src/parallel-ap';
import {race} from './src/race';
import {resolve} from './src/resolve';

Future[FL.of] = Future.of = resolve;
Future[FL.chainRec] = chainRec;

Future.prototype[FL.ap] = function Future$FL$ap(other){
var context = captureContext(nil, 'a Fantasy Land dispatch to ap', Future$FL$ap);
return other._transform(new ApTransformation(context, this));
};

Future.prototype[FL.map] = function Future$FL$map(mapper){
var context = captureContext(nil, 'a Fantasy Land dispatch to map', Future$FL$map);
return this._transform(new MapTransformation(context, mapper));
};

Future.prototype[FL.bimap] = function Future$FL$bimap(lmapper, rmapper){
var context = captureContext(nil, 'a Fantasy Land dispatch to bimap', Future$FL$bimap);
return this._transform(new BimapTransformation(context, lmapper, rmapper));
};

Future.prototype[FL.chain] = function Future$FL$chain(mapper){
var context = captureContext(nil, 'a Fantasy Land dispatch to chain', Future$FL$chain);
return this._transform(new ChainTransformation(context, mapper));
};

Future.prototype[FL.alt] = function Future$FL$alt(other){
var context = captureContext(nil, 'a Fantasy Land dispatch to alt', Future$FL$alt);
return this._transform(new AltTransformation(context, other));
};

function uncurry(f){
return function(a, b){
return f(a)(b);
};
}

export var Par = concurrify(Future, never, uncurry(race), uncurry(parallelAp));

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

function seq(par){
if(!isParallel(par)) throwInvalidArgument('seq', 0, 'be a ConcurrentFuture', par);
export function seq(par){
if(!isParallel(par)) throw invalidArgument('seq', 0, 'be a ConcurrentFuture', par);
return par.sequential;
}

export {Future, Future as default, Par, isParallel, seq};
export {isFuture, reject, resolve, resolve as of, never, isNever} from './src/future';
export * from './src/dispatchers/index';
export {after, rejectAfter} from './src/after';
export {attempt, attempt as try} from './src/attempt';
export {Future as default, Future};
export {isFuture, never, isNever} from './src/future';

export {after} from './src/after';
export {alt} from './src/alt';
export {and} from './src/and';
export {ap} from './src/ap';
export {attempt} from './src/attempt';
export {bimap} from './src/bimap';
export {both} from './src/both';
export {cache} from './src/cache';
export {chain} from './src/chain';
export {chainRej} from './src/chain-rej';
export {done} from './src/done';
export {encase} from './src/encase';
export {encase2} from './src/encase2';
export {encase3} from './src/encase3';
export {encaseN} from './src/encase-n';
export {encaseN2} from './src/encase-n2';
export {encaseN3} from './src/encase-n3';
export {encaseP} from './src/encase-p';
export {encaseP2} from './src/encase-p2';
export {encaseP3} from './src/encase-p3';
export {go, go as do} from './src/go';
export {extractLeft} from './src/extract-left';
export {extractRight} from './src/extract-right';
export {fold} from './src/fold';
export {forkCatch} from './src/fork-catch';
export {fork} from './src/fork';
export {go} from './src/go';
export {hook} from './src/hook';
export {lastly} from './src/lastly';
export {map} from './src/map';
export {mapRej} from './src/map-rej';
export {node} from './src/node';
export {parallelAp} from './src/parallel-ap';
export {parallel} from './src/parallel';
export {promise} from './src/promise';
export {race} from './src/race';
export {rejectAfter} from './src/reject-after';
export {reject} from './src/reject';
export {resolve} from './src/resolve';
export {swap} from './src/swap';
export {tryP} from './src/try-p';
export {value} from './src/value';

export {debugMode} from './src/internal/debug';
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
"lint": "eslint --ext mjs src test index.mjs index.cjs.mjs README.md",
"lint:readme": "remark --no-stdout --frail -u remark-validate-links README.md",
"release": "xyz --edit --repo [email protected]:fluture-js/Fluture.git --tag 'X.Y.Z' --script scripts/distribute --increment",
"test": "npm run lint && npm run lint:readme && npm run test:unit && npm run test:prop && npm run test:types && npm run test:build",
"test": "npm run lint && npm run lint:readme && npm run test:unit && npm run test:prop && npm run test:integration && npm run test:types && npm run test:build",
"test:integration": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --check-leaks --bail test/integration.js",
"test:unit": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --check-leaks --bail test/unit.js",
"test:prop": "npm run clean && mocha --require esm --ui bdd --reporter list --full-trace --bail test/prop.js",
"test:build": "npm run clean && npm run build && es-check es5 index.js && mocha --require esm --ui bdd --reporter dot --bail test/build.js",
"test:coverage": "npm run clean && nyc --extension .mjs --include src mocha --require esm --ui bdd --reporter dot test/unit.js test/prop.js || true",
"test:coverage": "npm run clean && nyc --extension .mjs --include src mocha --require esm --ui bdd --reporter dot test/unit.js test/prop.js test/integration.js || true",
"coverage:upload": "nyc report --reporter=text-lcov > coverage.lcov && codecov",
"coverage:report": "nyc report --reporter=html",
"test:types": "tsc --lib es6 index.d.ts"
Expand Down Expand Up @@ -83,6 +84,7 @@
"rollup-plugin-commonjs": "^9.1.6",
"rollup-plugin-node-resolve": "^4.0.0",
"sanctuary-benchmark": "^1.0.0",
"sanctuary-either": "^1.1.0",
"sanctuary-type-classes": "^11.0.0",
"typescript": "^3.0.3",
"xyz": "^3.0.0"
Expand Down
75 changes: 14 additions & 61 deletions src/after.mjs
Original file line number Diff line number Diff line change
@@ -1,70 +1,23 @@
import {Future, never} from './future';
import {show, partial1} from './internal/utils';
import {isUnsigned} from './internal/predicates';
import {throwInvalidArgument} from './internal/throw';
import {captureContext} from './internal/debug';
import {nil} from './internal/list';
import {application1, application, any, positiveInteger} from './internal/check';
import {createInterpreter, never} from './future';

export function After(time, value){
this._time = time;
this._value = value;
this.context = captureContext(nil, 'a Future created with after', After);
}

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

After.prototype._interpret = function After$interpret(rec, rej, res){
var id = setTimeout(res, this._time, this._value);
export var After = createInterpreter(2, 'after', function After$interpret(rec, rej, res){
var id = setTimeout(res, this.$1, this.$2);
return function After$cancel(){ clearTimeout(id) };
};
});

After.prototype.extractRight = function After$extractRight(){
return [this._value];
};

After.prototype.toString = function After$toString(){
return 'after(' + show(this._time) + ', ' + show(this._value) + ')';
return [this.$2];
};

export function RejectAfter(time, value){
this._time = time;
this._value = value;
this.context = captureContext(nil, 'a Future created with rejectAfter', After);
}

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

RejectAfter.prototype._interpret = function RejectAfter$interpret(rec, rej){
var id = setTimeout(rej, this._time, this._value);
return function RejectAfter$cancel(){ clearTimeout(id) };
};

RejectAfter.prototype.extractLeft = function RejectAfter$extractLeft(){
return [this._value];
};

RejectAfter.prototype.toString = function RejectAfter$toString(){
return 'rejectAfter(' + show(this._time) + ', ' + show(this._value) + ')';
};

function after$time(time, value){
return time === Infinity ? never : new After(time, value);
}

export function after(time, value){
if(!isUnsigned(time)) throwInvalidArgument('after', 0, 'be a positive Integer', time);
if(arguments.length === 1) return partial1(after$time, time);
return after$time(time, value);
}

function rejectAfter$time(time, reason){
return time === Infinity ? never : new RejectAfter(time, reason);
function alwaysNever(_){
return never;
}

export function rejectAfter(time, reason){
if(!isUnsigned(time)){
throwInvalidArgument('rejectAfter', 0, 'be a positive Integer', time);
}
if(arguments.length === 1) return partial1(rejectAfter$time, time);
return rejectAfter$time(time, reason);
export function after(time){
var context1 = application1(after, positiveInteger, time);
return time === Infinity ? alwaysNever : (function after(value){
var context2 = application(2, after, any, value, context1);
return new After(context2, time, value);
});
}
Loading

0 comments on commit 575a452

Please sign in to comment.