Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support FantasyLand 1.x #26

Merged
merged 5 commits into from
Sep 23, 2016
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Fluture

[<img src="https://raw.github.com/fantasyland/fantasy-land/master/logo.png" align="right" width="196" height="196" alt="Fantasy Land" />][1]

[![NPM Version](https://badge.fury.io/js/fluture.svg)](https://www.npmjs.com/package/fluture)
[![Dependencies](https://david-dm.org/avaq/fluture.svg)](https://david-dm.org/avaq/fluture)
[![Build Status](https://travis-ci.org/Avaq/Fluture.svg?branch=master)](https://travis-ci.org/Avaq/Fluture)
Expand Down Expand Up @@ -51,6 +49,7 @@ getPackageName('package.json')
## Table of contents

- [Usage](#usage)
- [Interoperability](#interoperability)
- [Documentation](#documentation)
1. [Type signatures](#type-signatures)
1. [Creating Futures](#creating-futures)
Expand Down Expand Up @@ -93,6 +92,13 @@ getPackageName('package.json')
- [Benchmarks](#benchmarks)
- [The name](#the-name)

## Interoperability

[<img src="https://raw.github.com/fantasyland/fantasy-land/master/logo.png" align="right" width="82" height="82" alt="Fantasy Land" />][1]

Fluture implements [FantasyLand 1.x][25] compatible `Functor`, `Bifunctor`,
`Apply`, `Applicative`, `Chain` and `Monad`.

## Documentation

### Type signatures
Expand Down Expand Up @@ -314,18 +320,17 @@ process.on('SIGINT', cancel);
```

#### ap
##### `#ap :: Future a (b -> c) ~> Future a b -> Future a c`
##### `#ap :: Future a b ~> Future a (b -> c) -> Future a c`
##### `.ap :: Apply m => m (a -> b) -> m a -> m b`

Apply the resolution value, which is expected to be a function (as in
`Future.of(a_function)`), to the resolution value in the given Future. Both
Futures involved will run in parallel, and if one rejects the resulting Future
will also be rejected. To learn more about the exact behaviour of `ap`, check
out its [spec][14].
Applies the function contained in the right-hand Future to the value contained
in the left-hand Future. Both Futures involved will run in parallel, and if one
rejects the resulting Future will also be rejected. To learn more about the
exact behaviour of `ap`, check out its [spec][14].

```js
Future.of(x => x + 1)
.ap(Future.of(1))
Future.of(1)
.ap(Future.of(x => x + 1))
.fork(console.error, console.log);
//> 2
```
Expand Down Expand Up @@ -802,3 +807,4 @@ means butterfly in Romanian; A creature you might expect to see in Fantasy Land.
[22]: https://github.com/Avaq/Fluture/wiki/Comparison-to-Promises
[23]: https://vimeo.com/106008027
[24]: https://github.com/fantasyland/fantasy-land#bifunctor
[25]: https://github.com/fantasyland/fantasy-land/tree/1.0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be 1.0.1?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish it could be ^1.0.0, maybe I should just use master and pin it once a 2.0 comes out.

46 changes: 19 additions & 27 deletions fluture.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@

const TYPEOF_FUTURE = 'fluture/Future';
const FL = {
map: 'map',
bimap: 'bimap',
chain: 'chain',
ap: 'ap',
of: 'of'
map: 'fantasy-land/map',
bimap: 'fantasy-land/bimap',
chain: 'fantasy-land/chain',
ap: 'fantasy-land/ap',
of: 'fantasy-land/of'
};

function isForkable(m){
Expand Down Expand Up @@ -222,7 +222,8 @@

function check$ap$f(f){
if(!isFunction(f)) throw new TypeError(
`Future#ap can only be used on Future<Function> but was used on a Future of: ${show(f)}`
'Future#ap expects its first argument to be a Future of a Function'
+ `\n Actual: Future.of(${show(f)})`
);
}

Expand Down Expand Up @@ -317,8 +318,8 @@

function check$parallel$m(m, i){
if(!isFuture(m)) throw new TypeError(
'Future.parallel expects argument 1 to be an array of Futures.'
+ ` The value at position ${i} in the array was not a Future.\n Actual: ${show(m)}`
'Future.parallel expects its second argument to be an array of Futures.'
+ ` The value at position ${i} in the array was not a Future\n Actual: ${show(m)}`
);
}

Expand Down Expand Up @@ -474,16 +475,16 @@
return new FutureClass(function Future$ap$fork(g, res){
let _f, _x, ok1, ok2, ko;
const rej = x => ko || (ko = 1, g(x));
const c1 = _this._f(rej, function Future$ap$resThis(f){
if(!ok2) return void (ok1 = 1, _f = f);
check$ap$f(f);
res(f(_x));
});
const c2 = m._f(rej, function Future$ap$resThat(x){
const c1 = _this._f(rej, function Future$ap$resThis(x){
if(!ok1) return void (ok2 = 1, _x = x)
check$ap$f(_f);
res(_f(x));
});
const c2 = m._f(rej, function Future$ap$resThat(f){
if(!ok2) return void (ok1 = 1, _f = f);
check$ap$f(f);
res(f(_x));
});
return function Future$ap$cancel(){ c1(); c2() };
});
}
Expand Down Expand Up @@ -701,15 +702,6 @@
};
}

//Creates a dispatcher for a unary method, but takes the object first rather than last.
function createInvertedUnaryDispatcher(method){
return function invertedUnaryDispatch(m, a){
if(arguments.length === 1) return unaryPartial(invertedUnaryDispatch, m);
if(m && typeof m[method] === 'function') return m[method](a);
error$invalidArgument(`Future.${method}`, 0, `have a "${method}" method`, m);
};
}

//Creates a dispatcher for a binary method.
function createBinaryDispatcher(method){
return function binaryDispatch(a, b, m){
Expand All @@ -730,13 +722,13 @@
};
}

Future.chain = createUnaryDispatcher('chain');
Future.chain = createUnaryDispatcher(FL.chain);
Future.recur = createUnaryDispatcher('recur');
Future.chainRej = createUnaryDispatcher('chainRej');
Future.map = createUnaryDispatcher('map');
Future.map = createUnaryDispatcher(FL.map);
Future.mapRej = createUnaryDispatcher('mapRej');
Future.bimap = createBinaryDispatcher('bimap');
Future.ap = createInvertedUnaryDispatcher('ap');
Future.bimap = createBinaryDispatcher(FL.bimap);
Future.ap = createUnaryDispatcher(FL.ap);
Future.swap = createNullaryDispatcher('swap');
Future.fork = createBinaryDispatcher('fork');
Future.race = createUnaryDispatcher('race');
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"codecov": "^1.0.1",
"data.task": "^3.0.0",
"eslint": "^3.0.1",
"fantasy-land": "^1.0.1",
"fun-task": "^1.1.1",
"istanbul": "^0.4.2",
"jsverify": "^0.7.1",
Expand Down
39 changes: 17 additions & 22 deletions test/fluture.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ const Future = require('../fluture');
const Readable = require('stream').Readable;
const jsc = require('jsverify');
const S = require('sanctuary');
const FL = {
map: 'map',
bimap: 'bimap',
chain: 'chain',
ap: 'ap',
of: 'of'
};
const FL = require('fantasy-land');

const noop = () => {};
const add = a => b => a + b;
Expand Down Expand Up @@ -726,14 +720,14 @@ describe('Future', () => {
fs.forEach(f => expect(f).to.throw(TypeError, /Future/));
});

it('throws TypeError when not not called on Future<Function>', () => {
it('throws TypeError when not not called with Future<Function>', () => {
const xs = [NaN, {}, [], 1, 'a', new Date, undefined, null];
const fs = xs.map(x => () => Future.of(x).ap(Future.of(1)).fork(noop, noop));
const fs = xs.map(x => () => Future.of(1).ap(Future.of(x)).fork(noop, noop));
fs.forEach(f => expect(f).to.throw(TypeError, /Future/));
});

it('applies its inner to the inner of the other', () => {
const actual = Future.of(add(1)).ap(Future.of(1));
it('calls the function contained in the given Future to its contained value', () => {
const actual = Future.of(1).ap(Future.of(add(1)));
return assertResolved(actual, 2);
});

Expand All @@ -747,19 +741,19 @@ describe('Future', () => {
});

it('does not matter if the left resolves late', () => {
const actual = Future.after(20, add(1)).ap(Future.of(1));
const actual = Future.after(20, 1).ap(Future.of(add(1)));
return assertResolved(actual, 2);
});

it('does not matter if the right resolves late', () => {
const actual = Future.of(add(1)).ap(Future.after(20, 1));
const actual = Future.of(1).ap(Future.after(20, add(1)));
return assertResolved(actual, 2);
});

it('forks in parallel', function(){
this.slow(80);
this.timeout(50);
const actual = Future.after(30, add(1)).ap(Future.after(30, 1));
const actual = Future.after(30, 1).ap(Future.after(30, add(1)));
return assertResolved(actual, 2);
});

Expand Down Expand Up @@ -1283,6 +1277,7 @@ describe('Fantasy-Land Compliance', function(){
const of = Future[FL.of];

const I = x => x;
const T = x => f => f(x);
const B = f => g => x => f(g(x));

const sub3 = x => x - 3;
Expand Down Expand Up @@ -1312,23 +1307,23 @@ describe('Fantasy-Land Compliance', function(){

describe('Apply', () => {
test('composition', x => eq(
of(sub3)[FL.map](B)[FL.ap](of(mul3))[FL.ap](of(x)),
of(sub3)[FL.ap](of(mul3)[FL.ap](of(x)))
of(x)[FL.ap](of(sub3)[FL.ap](of(mul3)[FL.map](B))),
of(x)[FL.ap](of(sub3))[FL.ap](of(mul3))
));
});

describe('Applicative', () => {
test('identity', x => eq(
of(x),
of(I)[FL.ap](of(x))
of(x)[FL.ap](of(I)),
of(x)
));
test('homomorphism', x => eq(
of(sub3(x)),
of(sub3)[FL.ap](of(x))
of(x)[FL.ap](of(sub3)),
of(sub3(x))
));
test('interchange', x => eq(
of(sub3)[FL.ap](of(x)),
of(f => f(x))[FL.ap](of(sub3))
of(x)[FL.ap](of(sub3)),
of(sub3)[FL.ap](of(T(x)))
));
});

Expand Down