Skip to content

Commit

Permalink
Merge pull request #411 from fluture-js/diego/bichain
Browse files Browse the repository at this point in the history
Add Future.bichain
  • Loading branch information
dicearr authored Feb 2, 2020
2 parents 549029c + d30bdb5 commit cc9361c
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 0 deletions.
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ for sponsoring the project.
- [`map`: Synchronously process the success value in a Future](#map)
- [`bimap`: Synchronously process the success or failure value in a Future](#bimap)
- [`chain`: Asynchronously process the success value in a Future](#chain)
- [`bichain`: Asynchronously process the success or failure value in a Future](#bichain)
- [`swap`: Swap the success with the failure value](#swap)
- [`mapRej`: Synchronously process the failure value in a Future](#maprej)
- [`chainRej`: Asynchronously process the failure value in a Future](#chainrej)
Expand Down Expand Up @@ -862,6 +863,42 @@ For comparison, an approximation with Promises is:
[resolution]: 42
```

#### bichain

```hs
bichain :: (a -> Future c d) -> (b -> Future c d) -> Future a b -> Future c d
```

Sequence a new Future using either the resolution or the rejection value from
another. Similarly to [`bimap`](#bimap), `bichain` expects two functions. But
instead of returning the new *value*, bichain expects Futures to be returned.

```js
> fork (log ('rejection'))
. (log ('resolution'))
. (bichain (resolve) (x => resolve (x + 1)) (resolve (41)))
[resolution]: 42

> fork (log ('rejection'))
. (log ('resolution'))
. (bichain (x => resolve (x + 1)) (resolve) (reject (41)))
[resolution]: 42
```

For comparison, an approximation with Promises is:

```js
> Promise.resolve (41)
. .then (x => Promise.resolve (x + 1), Promise.resolve)
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42

> Promise.reject (41)
. .then (Promise.resolve, x => Promise.resolve (x + 1))
. .then (log ('resolution'), log ('rejection'))
[resolution]: 42
```

#### swap

```hs
Expand Down
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ declare module 'fluture' {
/** 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>

/** Create a Future using the inner value of the given Future. See https://github.com/fluture-js/Fluture#bichain */
export function bichain<LA, LB, RB>(lmapper: (reason: LA) => FutureInstance<LB, RB>): <RA>(rmapper: (value: RA) => FutureInstance<LB, RB>) => (source: FutureInstance<LA, RA>) => FutureInstance<LB, RB>

/** Map over both branches of the given Bifunctor at once. See https://github.com/fluture-js/Fluture#bimap */
export function bimap<LA, LB>(lmapper: (reason: LA) => LB): <RA, RB>(rmapper: (value: RA) => RB) => (source: FutureInstance<LA, RA>) => FutureInstance<LB, RB>

Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export {ap} from './src/ap.js';
export {attemptP} from './src/attempt-p.js';
export {attempt} from './src/attempt.js';
export {bimap} from './src/bimap.js';
export {bichain} from './src/bichain.js';
export {both} from './src/both.js';
export {cache} from './src/cache.js';
export {chainRej} from './src/chain-rej.js';
Expand Down
18 changes: 18 additions & 0 deletions src/bichain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {createTransformation, future, application1, application, func} from './future.js';
import {call} from './internal/utils.js';

export var BichainTransformation = createTransformation(2, 'bichain', {
rejected: function BichainTransformation$rejected(x){ return call(this.$1, x) },
resolved: function BichainTransformation$resolved(x){ return call(this.$2, x) }
});

export function bichain(f){
var context1 = application1(bichain, func, arguments);
return function bichain(g){
var context2 = application(2, bichain, func, arguments, context1);
return function bichain(m){
var context3 = application(3, bichain, future, arguments, context1, context2);
return m._transform(new BichainTransformation(context3, f, g));
};
};
}
17 changes: 17 additions & 0 deletions test/prop/0.algebra.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
alt,
and,
ap,
bichain,
bimap,
cache,
chain,
Expand Down Expand Up @@ -54,6 +55,22 @@ property('ap composition using map', _mx, _mf, _mf, function (mx, mf, mg){
return eq(ap(mx)(ap(mf)(map(B)(mg))))(ap(ap(mx)(mf))(mg));
});

property('bichain associativity', _mx, _fm, _fm, function (m, f, g){
return eq(bichain(g)(g)(bichain(f)(f)(m)))(bichain(B(bichain(g)(g))(f))(B(bichain(g)(g))(f))(m));
});

property('bichain identity', _mx, function (m){
return eq(bichain(reject)(resolve)(m))(m);
});

property('bichain left identity', _x, _fm, _fm, function (x, f, g){
return eq(bichain(f)(g)(reject(x)))(f(x));
});

property('bichain left identity', _x, _fm, _fm, function (x, f, g){
return eq(bichain(f)(g)(resolve(x)))(g(x));
});

property('bimap identity', _mx, function (mx){
return eq(bimap(I)(I)(mx))(mx);
});
Expand Down
9 changes: 9 additions & 0 deletions test/prop/2.arbitrary.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {
after,
and,
bichain,
bimap,
chain,
chainRej,
Expand Down Expand Up @@ -90,6 +91,14 @@ property('mapRej(f)(m) = bimap(f)(I)(m)', anyFuture, function (m){
return eq(mapRej(f)(m))(bimap(f)(I)(m));
});

property('Rejected m => chainRej(B(mk)(f))(m) = bichain(B(mk)(f))(reject)(m)', make, anyRejectedFuture, function (mk, m){
return eq(chainRej(B(mk)(f))(m))(bichain(B(mk)(f))(reject)(m));
});

property('Resolved m => chain(B(mk)(f))(m) = bichain(resolve)(B(mk)(f))(m)', make, anyResolvedFuture, function (mk, m){
return eq(chain(B(mk)(f))(m))(bichain(resolve)(B(mk)(f))(m));
});

property('mapRej(f)(m) = swap(map(f)(swap(m)))', anyFuture, function (m){
return eq(mapRej(f)(m))(swap(map(f)(swap(m))));
});
Expand Down
26 changes: 26 additions & 0 deletions test/unit/4.bichain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {bichain} from '../../index.js';
import {test, assertCrashed, assertRejected, assertResolved, assertValidFuture, eq, throwing, error} from '../util/util.js';
import {testFunction, functionArg, futureArg} from '../util/props.js';
import {resolvedSlow, resolved, rejected, rejectedSlow} from '../util/futures.js';
import {resolve, reject} from '../../index.js';

testFunction('bichain', bichain, [functionArg, functionArg, futureArg], assertValidFuture);

test('runs a bichain transformation on Futures', function (){
return Promise.all([
assertResolved(bichain(reject)(resolve)(resolved), 'resolved'),
assertResolved(bichain(reject)(resolve)(resolvedSlow), 'resolvedSlow'),
assertRejected(bichain(resolve)(reject)(resolved), 'resolved'),
assertRejected(bichain(resolve)(reject)(resolvedSlow), 'resolvedSlow'),
assertResolved(bichain(resolve)(reject)(rejected), 'rejected'),
assertResolved(bichain(resolve)(reject)(rejectedSlow), 'rejectedSlow'),
assertRejected(bichain(reject)(resolve)(rejected), 'rejected'),
assertRejected(bichain(reject)(resolve)(rejectedSlow), 'rejectedSlow'),
assertCrashed(bichain(throwing)(reject)(rejected), error),
assertCrashed(bichain(resolve)(throwing)(resolved), error),
]);
});

test('displays correctly as string', function (){
eq(bichain(resolve)(reject)(resolved).toString(), 'bichain (' + resolve.toString() + ') (' + reject.toString() + ') (resolve ("resolved"))');
});

0 comments on commit cc9361c

Please sign in to comment.