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 3 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
41 changes: 21 additions & 20 deletions test/fluture.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ 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'
map: 'fantasy-land/map',
bimap: 'fantasy-land/bimap',
chain: 'fantasy-land/chain',
ap: 'fantasy-land/ap',
of: 'fantasy-land/of'
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest adding fantasy-land as a dev dependency and requiring it here to catch typos. :)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh, that's a great idea! That way we're ensuring compatibility with the naming conventions of a specific FL version, without having a hard dependency to it! :D

Choose a reason for hiding this comment

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

Just curious why not actually add a hard dependency?

Choose a reason for hiding this comment

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

I mean I can understand not wanting a peer dependency, but what bad about a normal dependency?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious why not actually add a hard dependency?

I don't see compelling reasons to do so: sanctuary-js/sanctuary-type-classes#2 (comment).

Copy link
Member Author

Choose a reason for hiding this comment

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

FantasyLand comes with its own dependency tree which seems to me like unnecessary overhead. It's not much of a big deal, but if there's no practical difference, or even much of a code-reuse advantage, I consider it cleaner not to depend on it.

Copy link

@rpominov rpominov Sep 18, 2016

Choose a reason for hiding this comment

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

FantasyLand comes with its own dependency tree which seems to me like unnecessary overhead.

Didn't know that, created a PR that fixes it fantasyland/fantasy-land#168

Other points are valid as well though, I understand it better now, thanks!


const noop = () => {};
Expand Down Expand Up @@ -726,14 +726,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 +747,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 +1283,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 +1313,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