Skip to content

Commit

Permalink
Merge pull request #102 from fluture-js/avaq/sanctuary-type-identifie…
Browse files Browse the repository at this point in the history
…rs-2

Improve error messages related to incompatible Futures
  • Loading branch information
Avaq committed May 12, 2017
2 parents 7fdca2e + 01bb5dd commit e0c2d71
Show file tree
Hide file tree
Showing 24 changed files with 230 additions and 74 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ getPackageName('package.json')
* [isNever](#isnever)
1. [Sanctuary](#sanctuary)
1. [Futurization](#futurization)
1. [Casting Futures](#casting-futures)
- [Benchmarks](#benchmarks)
- [Butterfly](#butterfly)

Expand Down Expand Up @@ -987,6 +988,25 @@ readFile('README.md', 'utf8')
//> "# [![Fluture](logo.png)](#butterfly)"
```

### Casting Futures

Sometimes you may need to convert one Future to another, for example when the
Future was created by another package, or an incompatible version of Fluture.

When [`isFuture`](#isfuture) returns `false`, a conversion is necessary. Usually
the most concise way of doing this is as follows:

```js
const NoFuture = require('incompatible-future');
const incompatible = NoFuture.of('Hello');

//Cast the incompatible Future to our version of Future:
const compatible = Future(incompatible.fork.bind(incompatible));

compatible.both(Future.of('world')).value(console.log);
//> ["Hello", "world"]
```

## Benchmarks

Simply run `node ./bench/<file>` to see how a specific method compares to
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"concurrify": "^0.1.0",
"inspect-f": "^1.2.0",
"sanctuary-type-classes": "^3.0.0",
"sanctuary-type-identifiers": "^1.0.0"
"sanctuary-type-identifiers": "^2.0.0"
},
"devDependencies": {
"benchmark": "^2.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/cache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Core, isFuture} from './core';
import {noop} from './internal/fn';
import {invalidArgument} from './internal/throw';
import {invalidFuture} from './internal/throw';

const Cold = Cached.Cold = 0;
const Pending = Cached.Pending = 1;
Expand Down Expand Up @@ -113,6 +113,6 @@ Cached.prototype.toString = function Cached$toString(){
};

export function cache(m){
if(!isFuture(m)) invalidArgument('Future.cache', 0, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.cache', 0, m);
return new Cached(m);
}
40 changes: 21 additions & 19 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import Denque from 'denque';

import {show, showf, noop, moop} from './internal/fn';
import {isFunction} from './internal/is';
import {error, typeError, invalidArgument, invalidContext} from './internal/throw';
import {FL} from './internal/const';
import {error, typeError, invalidArgument, invalidContext, invalidFuture} from './internal/throw';
import {FL, $$type} from './internal/const';
import type from 'sanctuary-type-identifiers';

const TYPEOF_FUTURE = 'fluture/Future@2';

const throwRejection = x => error(
`Future#value was called on a rejected Future\n Actual: Future.reject(${show(x)})`
);
Expand All @@ -20,7 +18,7 @@ export function Future(computation){
}

export function isFuture(x){
return x instanceof Future || type(x) === TYPEOF_FUTURE;
return x instanceof Future || type(x) === $$type;
}

Future.prototype[FL.ap] = function Future$FL$ap(other){
Expand All @@ -41,7 +39,7 @@ Future.prototype[FL.chain] = function Future$FL$chain(mapper){

Future.prototype.ap = function Future$ap(other){
if(!isFuture(this)) invalidContext('Future#ap', this);
if(!isFuture(other)) invalidArgument('Future#ap', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#ap', 0, other);
return this._ap(other);
};

Expand Down Expand Up @@ -78,25 +76,25 @@ Future.prototype.chainRej = function Future$chainRej(mapper){

Future.prototype.race = function Future$race(other){
if(!isFuture(this)) invalidContext('Future#race', this);
if(!isFuture(other)) invalidArgument('Future#race', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#race', 0, other);
return this._race(other);
};

Future.prototype.both = function Future$both(other){
if(!isFuture(this)) invalidContext('Future#both', this);
if(!isFuture(other)) invalidArgument('Future#both', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#both', 0, other);
return this._both(other);
};

Future.prototype.and = function Future$and(other){
if(!isFuture(this)) invalidContext('Future#and', this);
if(!isFuture(other)) invalidArgument('Future#and', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#and', 0, other);
return this._and(other);
};

Future.prototype.or = function Future$or(other){
if(!isFuture(this)) invalidContext('Future#or', this);
if(!isFuture(other)) invalidArgument('Future#or', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#or', 0, other);
return this._or(other);
};

Expand All @@ -114,13 +112,13 @@ Future.prototype.fold = function Future$fold(lmapper, rmapper){

Future.prototype.finally = function Future$finally(other){
if(!isFuture(this)) invalidContext('Future#finally', this);
if(!isFuture(other)) invalidArgument('Future#finally', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#finally', 0, other);
return this._finally(other);
};

Future.prototype.lastly = function Future$lastly(other){
if(!isFuture(this)) invalidContext('Future#lastly', this);
if(!isFuture(other)) invalidArgument('Future#lastly', 0, 'to be a Future', other);
if(!isFuture(other)) invalidFuture('Future#lastly', 0, other);
return this._finally(other);
};

Expand Down Expand Up @@ -429,9 +427,11 @@ export class BimapAction extends Action{
resolved(x){ return new Resolved(this.rmapper(x)) }
toString(){ return `bimap(${showf(this.lmapper)}, ${showf(this.rmapper)})` }
}
const check$chain = (m, f, x) => isFuture(m) ? m : typeError(
'Future#chain expects the function its given to return a Future'
+ `\n Actual: ${show(m)}\n From calling: ${showf(f)}\n With: ${show(x)}`
const check$chain = (m, f, x) => isFuture(m) ? m : invalidFuture(
'Future#chain',
'the function its given to return a Future',
m,
`\n From calling: ${showf(f)}\n With: ${show(x)}`
);
export class ChainAction extends Action{
constructor(mapper){ super(); this.mapper = mapper }
Expand All @@ -443,9 +443,11 @@ export class MapRejAction extends Action{
rejected(x){ return new Rejected(this.mapper(x)) }
toString(){ return `mapRej(${showf(this.mapper)})` }
}
const check$chainRej = (m, f, x) => isFuture(m) ? m : typeError(
'Future#chainRej expects the function its given to return a Future'
+ `\n Actual: ${show(m)}\n From calling: ${showf(f)}\n With: ${show(x)}`
const check$chainRej = (m, f, x) => isFuture(m) ? m : invalidFuture(
'Future#chainRej',
'the function its given to return a Future',
m,
`\n From calling: ${showf(f)}\n With: ${show(x)}`
);
export class ChainRejAction extends Action{
constructor(mapper){ super(); this.mapper = mapper }
Expand Down Expand Up @@ -642,5 +644,5 @@ Sequence.prototype.toString = function Sequence$toString(){
return `${this._spawn.toString()}${this._actions.map(x => `.${x.toString()}`).join('')}`;
};

Future['@@type'] = TYPEOF_FUTURE;
Future['@@type'] = $$type;
Future[FL.of] = of;
6 changes: 3 additions & 3 deletions src/dispatchers/and.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

function and$left(left, right){
if(!isFuture(right)) invalidArgument('Future.and', 1, 'be a Future', right);
if(!isFuture(right)) invalidFuture('Future.and', 1, right);
return left.and(right);
}

export function and(left, right){
if(!isFuture(left)) invalidArgument('Future.and', 0, 'be a Future', left);
if(!isFuture(left)) invalidFuture('Future.and', 0, left);
if(arguments.length === 1) return partial1(and$left, left);
return and$left(left, right);
}
6 changes: 3 additions & 3 deletions src/dispatchers/both.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

function both$left(left, right){
if(!isFuture(right)) invalidArgument('Future.both', 1, 'be a Future', right);
if(!isFuture(right)) invalidFuture('Future.both', 1, right);
return left.both(right);
}

export function both(left, right){
if(!isFuture(left)) invalidArgument('Future.both', 0, 'be a Future', left);
if(!isFuture(left)) invalidFuture('Future.both', 0, left);
if(arguments.length === 1) return partial1(both$left, left);
return both$left(left, right);
}
4 changes: 2 additions & 2 deletions src/dispatchers/chain-rej.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {isFunction} from '../internal/is';
import {invalidArgument} from '../internal/throw';
import {invalidArgument, invalidFuture} from '../internal/throw';

function chainRej$chainer(chainer, m){
if(!isFuture(m)) invalidArgument('Future.chainRej', 1, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.chainRej', 1, m);
return m.chainRej(chainer);
}

Expand Down
4 changes: 2 additions & 2 deletions src/dispatchers/extract-left.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isFuture} from '../core';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

export function extractLeft(m){
if(!isFuture(m)) invalidArgument('Future.extractLeft', 0, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.extractLeft', 0, m);
return m.extractLeft();
}
4 changes: 2 additions & 2 deletions src/dispatchers/extract-right.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isFuture} from '../core';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

export function extractRight(m){
if(!isFuture(m)) invalidArgument('Future.extractRight', 0, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.extractRight', 0, m);
return m.extractRight();
}
4 changes: 2 additions & 2 deletions src/dispatchers/fold.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {isFuture} from '../core';
import {partial1, partial2} from '../internal/fn';
import {isFunction} from '../internal/is';
import {invalidArgument} from '../internal/throw';
import {invalidArgument, invalidFuture} from '../internal/throw';

function fold$f$g(f, g, m){
if(!isFuture(m)) invalidArgument('Future.fold', 2, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.fold', 2, m);
return m.fold(f, g);
}

Expand Down
4 changes: 2 additions & 2 deletions src/dispatchers/fork.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {isFuture} from '../core';
import {partial1, partial2} from '../internal/fn';
import {isFunction} from '../internal/is';
import {invalidArgument} from '../internal/throw';
import {invalidArgument, invalidFuture} from '../internal/throw';

function fork$f$g(f, g, m){
if(!isFuture(m)) invalidArgument('Future.fork', 2, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.fork', 2, m);
return m._fork(f, g);
}

Expand Down
6 changes: 3 additions & 3 deletions src/dispatchers/lastly.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

function lastly$right(right, left){
if(!isFuture(left)) invalidArgument('Future.finally', 1, 'be a Future', left);
if(!isFuture(left)) invalidFuture('Future.finally', 1, left);
return left.finally(right);
}

export function lastly(right, left){
if(!isFuture(right)) invalidArgument('Future.finally', 0, 'be a Future', right);
if(!isFuture(right)) invalidFuture('Future.finally', 0, right);
if(arguments.length === 1) return partial1(lastly$right, right);
return lastly$right(right, left);
}
4 changes: 2 additions & 2 deletions src/dispatchers/map-rej.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {isFunction} from '../internal/is';
import {invalidArgument} from '../internal/throw';
import {invalidArgument, invalidFuture} from '../internal/throw';

function mapRej$mapper(mapper, m){
if(!isFuture(m)) invalidArgument('Future.mapRej', 1, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.mapRej', 1, m);
return m.mapRej(mapper);
}

Expand Down
6 changes: 3 additions & 3 deletions src/dispatchers/or.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

function or$left(left, right){
if(!isFuture(right)) invalidArgument('Future.or', 1, 'be a Future', right);
if(!isFuture(right)) invalidFuture('Future.or', 1, right);
return left.or(right);
}

export function or(left, right){
if(!isFuture(left)) invalidArgument('Future.or', 0, 'be a Future', left);
if(!isFuture(left)) invalidFuture('Future.or', 0, left);
if(arguments.length === 1) return partial1(or$left, left);
return or$left(left, right);
}
4 changes: 2 additions & 2 deletions src/dispatchers/promise.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isFuture} from '../core';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

export function promise(m){
if(!isFuture(m)) invalidArgument('Future.promise', 0, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.promise', 0, m);
return m.promise();
}
6 changes: 3 additions & 3 deletions src/dispatchers/race.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

function race$right(right, left){
if(!isFuture(left)) invalidArgument('Future.race', 1, 'be a Future', left);
if(!isFuture(left)) invalidFuture('Future.race', 1, left);
return left.race(right);
}

export function race(right, left){
if(!isFuture(right)) invalidArgument('Future.race', 0, 'be a Future', right);
if(!isFuture(right)) invalidFuture('Future.race', 0, right);
if(arguments.length === 1) return partial1(race$right, right);
return race$right(right, left);
}
4 changes: 2 additions & 2 deletions src/dispatchers/swap.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {isFuture} from '../core';
import {invalidArgument} from '../internal/throw';
import {invalidFuture} from '../internal/throw';

export function swap(m){
if(!isFuture(m)) invalidArgument('Future.swap', 0, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.swap', 0, m);
return m.swap();
}
4 changes: 2 additions & 2 deletions src/dispatchers/value.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {isFuture} from '../core';
import {partial1} from '../internal/fn';
import {isFunction} from '../internal/is';
import {invalidArgument} from '../internal/throw';
import {invalidArgument, invalidFuture} from '../internal/throw';

function value$cont(cont, m){
if(!isFuture(m)) invalidArgument('Future.value', 1, 'be a Future', m);
if(!isFuture(m)) invalidFuture('Future.value', 1, m);
return m.value(cont);
}

Expand Down
11 changes: 6 additions & 5 deletions src/go.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Core, isFuture} from './core';
import {isFunction, isIterator} from './internal/is';
import {isIteration} from './internal/iteration';
import {show, showf, noop} from './internal/fn';
import {invalidArgument, typeError} from './internal/throw';
import {invalidArgument, typeError, invalidFuture} from './internal/throw';
import {Undetermined, Synchronous, Asynchronous} from './internal/timing';

const check$iterator = g => isIterator(g) ? g : invalidArgument(
Expand All @@ -18,10 +18,11 @@ const check$iteration = o => {
+ `\n Actual: ${show(o)}`
);
if(o.done || isFuture(o.value)) return o;
return typeError(
'A non-Future was produced by iterator.next() in Future.do.'
+ ' If you\'re using a generator, make sure you always `yield` a Future'
+ `\n Actual: ${o.value}`
return invalidFuture(
'Future.do',
'the iterator to produce only valid Futures',
o.value,
'\n Tip: If you\'re using a generator, make sure you always yield a Future'
);
};

Expand Down
Loading

0 comments on commit e0c2d71

Please sign in to comment.