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

Improve error messages related to incompatible Futures #102

Merged
merged 1 commit into from
May 12, 2017
Merged
Show file tree
Hide file tree
Changes from all 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
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