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 13, 2019
1 parent c6c2097 commit 4c8586a
Show file tree
Hide file tree
Showing 164 changed files with 2,467 additions and 4,646 deletions.
48 changes: 7 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ for sponsoring the project.
- [`rejectAfter`: Create a Future that rejects after a timeout](#rejectafter)
- [`do`: Create a "coroutine" using a generator function](#do)
- [`try`: Create a Future using a possibly throwing function](#try)
- [`tryP`: Create a Future using a Promise-returning function](#tryp)
- [`attemptP`: Create a Future using a Promise-returning function](#attemptp)
- [`node`: Create a Future using a Node-style callback](#node)
- [`encase`: Convert a possibly throwing function to a Future function](#encase)
- [`encaseP`: Convert a Promise-returning function to a Future function](#encasep)
Expand All @@ -165,7 +165,7 @@ for sponsoring the project.

<details><summary>Converting between Promises and Futures</summary>

- [`tryP`: Create a Future using a Promise-returning function](#tryp)
- [`attemptP`: Create a Future using a Promise-returning function](#attemptp)
- [`encaseP`: Convert a Promise-returning function to a Future function](#encasep)
- [`promise`: Convert a Future to a Promise](#promise)

Expand Down Expand Up @@ -669,12 +669,12 @@ Future.try(() => data.foo.bar.baz)
//> [TypeError: Cannot read property 'baz' of undefined]
```

#### tryP
#### attemptP

<details><summary><code>tryP :: (() -> Promise e r) -> Future e r</code></summary>
<details><summary><code>attemptP :: (() -> Promise e r) -> Future e r</code></summary>

```hs
tryP :: (() -> Promise e r) -> Future e r
attemptP :: (() -> Promise e r) -> Future e r
```

</details>
Expand All @@ -685,7 +685,7 @@ resolves with its resolution value, or rejects with its rejection reason.
Short for [`Future.encaseP(f, undefined)`](#encasep).

```js
Future.tryP(() => Promise.resolve('Hello'))
Future.attemptP(() => Promise.resolve('Hello'))
.fork(console.error, console.log);
//> "Hello"
```
Expand Down Expand Up @@ -772,7 +772,7 @@ Furthermore; `encaseP2` and `encaseP3` are binary and ternary versions of
var fetchf = Future.encaseP(fetch);

fetchf('https://api.github.com/users/Avaq')
.chain(res => Future.tryP(_ => res.json()))
.chain(res => Future.attemptP(_ => res.json()))
.map(user => user.name)
.fork(console.error, console.log);
//> "Aldwin Vlasblom"
Expand Down 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);
// });
// };
4 changes: 2 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,8 @@ declare module 'fluture' {
/** Swap the rejection reason and the resolution value. See https://github.com/fluture-js/Fluture#swap */
export function swap<L, R>(source: FutureInstance<L, R>): FutureInstance<R, L>

/** Convert a Promise-returning function to a Future. See https://github.com/fluture-js/Fluture#tryP */
export function tryP<L, R>(fn: () => Promise<R>): FutureInstance<L, R>
/** Convert a Promise-returning function to a Future. See https://github.com/fluture-js/Fluture#attemptP */
export function attemptP<L, R>(fn: () => Promise<R>): FutureInstance<L, R>

/** Fork the Future into the given continuation. See https://github.com/fluture-js/Fluture#value */
export function value<R>(resolve: ResolveFunction<R>, source: FutureInstance<never, R>): Cancel
Expand Down
127 changes: 89 additions & 38 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -1,54 +1,105 @@
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] = 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 {attemptP} from './src/attempt-p';
export {attempt} from './src/attempt';
export {bimap} from './src/bimap';
export {both} from './src/both';
export {cache} from './src/cache';
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 {chainRej} from './src/chain-rej';
export {chain} from './src/chain';
export {done} from './src/done';
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 {encase} from './src/encase';
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 {mapRej} from './src/map-rej';
export {map} from './src/map';
export {node} from './src/node';
export {parallelAp} from './src/parallel-ap';
export {parallel} from './src/parallel';
export {tryP} from './src/try-p';
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 {value} from './src/value';

export {debugMode} from './src/internal/debug';
7 changes: 4 additions & 3 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 @@ -71,7 +72,6 @@
"eslint-plugin-markdown": "^1.0.0-beta.7",
"esm": "^3.0.81",
"fantasy-laws": "^1.0.1",
"fantasy-states": "^0.2.1",
"jsverify": "^0.8.3",
"mocha": "^6.0.0",
"nyc": "^13.0.1",
Expand All @@ -83,6 +83,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
Loading

0 comments on commit 4c8586a

Please sign in to comment.