Comparing version 0.0.2 to 0.0.3
@@ -0,3 +1,20 @@ | ||
/* A simple demonstration of using Pacta to compose asynchronous HTTP requests. | ||
* | ||
* When run, this program will output four random code names as generated by | ||
* Clive Murray's http://codenames.clivemurray.com. | ||
* | ||
* In this example, http.getJSON() returns a Promise of the parsed JSON from the | ||
* given URL. For these specific URLs, an array of objects will be returned (one | ||
* URL returns a list of "prefixes" and the other returns a list of animal | ||
* names). | ||
* | ||
* Due to the fact two arrays are returned, concatenating the two promises | ||
* together would join them into one but we actually want to keep them separate | ||
* so that we can choose a random entry from each. To do this, we use append to | ||
* append each promise's value to an empty array. | ||
* | ||
* See examples/codenames-2.js for another way to achieve the same result. | ||
*/ | ||
var http = require('./promised-http'), | ||
getJSON = http.getJSON; | ||
Promise = require('../lib/pacta').Promise; | ||
@@ -8,8 +25,8 @@ var random = function (coll) { | ||
var promisedPrefixes = getJSON('http://codenames.clivemurray.com/data/prefixes.json'), | ||
promisedAnimals = getJSON('http://codenames.clivemurray.com/data/animals.json'), | ||
prefixesAndAnimals = promisedPrefixes.combine(promisedAnimals); | ||
var promisedPrefixes = http.getJSON('http://codenames.clivemurray.com/data/prefixes.json'), | ||
promisedAnimals = http.getJSON('http://codenames.clivemurray.com/data/animals.json'), | ||
prefixesAndAnimals = Promise.of([]).append(promisedPrefixes).append(promisedAnimals); | ||
var promisedCodeName = function () { | ||
return prefixesAndAnimals.explode(function (prefixes, animals) { | ||
return prefixesAndAnimals.spread(function (prefixes, animals) { | ||
var prefix = random(prefixes), | ||
@@ -16,0 +33,0 @@ animal = random(animals); |
@@ -88,20 +88,16 @@ var events = require('events'); | ||
Promise.prototype.conjoin = function (other) { | ||
var list = function (x) { return [].concat(x); }; | ||
var wrap = function (x) { return [].concat(x); }; | ||
return this.map(list).concat(other.map(list)); | ||
return this.map(wrap).concat(other.map(wrap)); | ||
}; | ||
/* combine :: Promise [a] -> Promise [b] -> Promise [[a] [b]] | ||
* combine :: Promise [a] -> Promise b -> Promise [[a] b] | ||
* combine :: Promise a -> Promise [b] -> Promise [a [b]] | ||
* combine :: Promise a -> Promise b -> Promise [a b] | ||
*/ | ||
Promise.prototype.combine = function (other) { | ||
var wrap = function (x) { return [x]; }; | ||
return this.map(wrap).concat(other.map(wrap)); | ||
/* append :: Promise [a] -> Promise b -> Promise [a b] */ | ||
Promise.prototype.append = function (other) { | ||
return this.concat(other.map(function (x) { | ||
return [x]; | ||
})); | ||
}; | ||
/* explode :: Promise a -> (a -> b) -> Promise b */ | ||
Promise.prototype.explode = function (f) { | ||
/* spread :: Promise a -> (a -> b) -> Promise b */ | ||
Promise.prototype.spread = Promise.prototype.explode = function (f) { | ||
return this.map(function (x) { | ||
@@ -108,0 +104,0 @@ return f.apply(null, x); |
@@ -7,3 +7,3 @@ { | ||
"keywords": ["promises", "monad", "functor"], | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"main": "./lib/pacta.js", | ||
@@ -10,0 +10,0 @@ "dependencies": {}, |
277
README.md
# pacta [![Build Status](https://travis-ci.org/mudge/pacta.png?branch=master)](https://travis-ci.org/mudge/pacta) | ||
This is an implementation of algebraic Promises in | ||
[node.js](http://nodejs.org) having been convinced by [James | ||
Coglan](http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-promises-are-functional-nodes-biggest-missed-opportunity/) | ||
and [Aanand Prasad](http://aanandprasad.com/articles/negronis/). | ||
[node.js](http://nodejs.org). | ||
Pacta's promises can be used as the following algebraic structures: | ||
Promises can be thought of as objects representing a value that may not have | ||
been calculated yet (similar to the [Maybe monad][Maybe]). An obvious example | ||
is the result of an asynchronous HTTP request: it's not clear *when* | ||
the request will be fulfilled but it will be at some point in the future. | ||
Having actual Promise objects representing these eventual values allows you | ||
to compose, transform and act on them without worrying about their time or | ||
sequence of execution. | ||
For a worked example of this, see the | ||
[two](https://github.com/mudge/pacta/blob/master/example/codenames.js) | ||
[example programs](https://github.com/mudge/pacta/blob/master/example/codenames-2.js) | ||
and [sample HTTP | ||
client](https://github.com/mudge/pacta/blob/master/example/promised-http.js) | ||
included in Pacta. | ||
Pacta's promises can be used as the following algebraic structures as defined | ||
in the [Fantasy Land | ||
Specification](https://github.com/puffnfresh/fantasy-land): | ||
* [Semigroups](https://github.com/puffnfresh/fantasy-land#semigroup) (through | ||
`Promise#concat` which concatenates promises containing semigroups such as | ||
[`Promise#concat`](#promiseconcatp) which concatenates promises containing semigroups such as | ||
arrays and strings); | ||
* [Monoids](https://github.com/puffnfresh/fantasy-land#monoid) (through | ||
`Promise#empty` which returns an empty version of a promise that contains a | ||
[`Promise#empty`](#promiseempty) which returns an empty version of a promise that contains a | ||
monoid); | ||
* [Functors](https://github.com/puffnfresh/fantasy-land#functor) (through | ||
`Promise#map`); | ||
[`Promise#map`](#promisemapf)); | ||
* [Applicative](https://github.com/puffnfresh/fantasy-land#applicative) | ||
(through `Promise#ap` and `Promise.of`); | ||
* [Chains](https://github.com/puffnfresh/fantasy-land#chain) (through `Promise#chain`); | ||
(through [`Promise#ap`](#promiseapp) and [`Promise.of`](#promiseofx)); | ||
* [Chains](https://github.com/puffnfresh/fantasy-land#chain) (through [`Promise#chain`](#promisechainf)); | ||
* [Monads](https://github.com/puffnfresh/fantasy-land#monad) (through all of | ||
the above). | ||
Above that, Pacta also provides: | ||
As well as above, Pacta also provides the following functions for creating and | ||
working with Promises of lists: | ||
* `conjoin` to concatenate promises into a list of values regardless of their | ||
original type meaning that non-Monoid types can be combined with others | ||
(e.g. a promise of `'foo'` can be conjoined with `[1, 2]` to produce | ||
`['foo', 1, 2]`); | ||
* `combine` to conjoin promises without flattening lists (e.g. combining a | ||
promise of `[1, 2]` and `[3]` will give a promise of `[[1, 2], [3]]` instead | ||
of `[1, 2, 3]` as it would with `concat` and `conjoin`); | ||
* `explode` to map over a promise's value but, instead of receiving a single | ||
value, explode the promise's value into seperate arguments: | ||
* [`Promise#conjoin`](#promiseconjoinp) to concatenate promises into a list of | ||
values regardless of their original type meaning that non-Monoid types can | ||
be combined with others (e.g. a promise of `'foo'` can be conjoined with | ||
`[1, 2]` to produce `['foo', 1, 2]`); | ||
* [`Promise#append`](#promiseappendp) to append promises to an initial promise | ||
of a list. This means that you can work more easily with multiple promises | ||
of lists without joining them together (as would be done with `concat` and | ||
`conjoin`), e.g. appending a promise of `[2, 3]` to a promise of `[1]` | ||
results in `[1, [2, 3]]` rather than `[1, 2, 3]`); | ||
* [`Promise#spread`](#promisespreadf) to map over a promise's value but, | ||
instead of receiving a single value, spread the promise's value across | ||
separate arguments: | ||
```javascript | ||
Promise.of([1, 2]).explode(function (x, y) { | ||
Promise.of([1, 2]).spread(function (x, y) { | ||
console.log(x); //=> 1 | ||
@@ -54,8 +73,8 @@ console.log(y); //=> 2 | ||
See the [HTTP client | ||
example](https://github.com/mudge/pacta/blob/master/example/codenames.js) and | ||
[the test | ||
See [the test | ||
suite](https://github.com/mudge/pacta/blob/master/test/pacta_test.js) for more | ||
information. | ||
[Maybe]: https://en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad | ||
## Usage | ||
@@ -84,18 +103,206 @@ | ||
p2.concat(p3).map(function (x) { | ||
console.log(x); //=> [ 'bar', 'baz' ] | ||
}); | ||
p2.concat(p3).map(console.log); //=> [ 'bar', 'baz' ] | ||
p.conjoin(p2).map(function (x) { | ||
console.log(x); //=> [ 'Foo', 'bar' ] | ||
}); | ||
p.conjoin(p2).map(console.log); //=> [ 'Foo', 'bar' ] | ||
p.combine(p2).map(function (x) { | ||
console.log(x); //=> [ 'Foo', [ 'bar' ] ] | ||
}); | ||
Promise.of([]).append(p).append(p2).map(console.log); | ||
//=> [ 'Foo', [ 'bar' ] ] | ||
p.combine(p2).explode(function (x, y) { | ||
console.log(x); //=> Foo | ||
console.log(y); //=> [ 'bar' ] | ||
p2.append(p).explode(function (x, y) { | ||
console.log(x); //=> 'bar' | ||
console.log(y); //=> 'Foo' | ||
}); | ||
``` | ||
## API Documentation | ||
### `Promise()` | ||
```javascript | ||
var promise = new Promise(); | ||
``` | ||
Create a new, unfulfilled promise that will eventually be populated with a | ||
value (through [`resolve`](#promiseresolvex)). | ||
### `Promise.of(x)` | ||
```javascript | ||
var promise = Promise.of(1); | ||
var promise = Promise.of('foo'); | ||
``` | ||
Create a new, fulfilled promise already populated with a value `x`. | ||
### `Promise#resolve(x)` | ||
```javascript | ||
var promise = new Promise(); | ||
promise.resolve(5); | ||
``` | ||
Populate a promise with the value `x` thereby resolving it. | ||
### `Promise#map(f)` | ||
```javascript | ||
var promise = Promise.of(2); | ||
promise.map(function (x) { | ||
console.log(x); | ||
return x * 2; | ||
}); //=> Promise.of(4) | ||
``` | ||
Execute a function `f` on the contents of the promise. This returns a new | ||
promise containing the result of applying `f` to the initial promise's value. | ||
In [Haskell](http://www.haskell.org) notation, its type signature is: | ||
```haskell | ||
map :: Promise a -> (a -> b) -> Promise b | ||
``` | ||
Note that this is the primary way of acting on the value of a promise: you can | ||
use side-effects within your given function (e.g. `console.log`) as well as | ||
modifying the value and returning it in order to affect the returning | ||
promise. | ||
### `Promise#concat(p)` | ||
```javascript | ||
var promise = Promise.of('foo'), | ||
promise2 = Promise.of('bar'); | ||
promise.concat(promise2); //=> Promise.of('foobar') | ||
``` | ||
Concatenate the promise with another promise `p` into one containing both | ||
values concatenated together. This will work for any promise containing a | ||
semigroup (viz. a value that supports `concat`) such as `String` or `Array`. | ||
Note that [`concat`'s usual | ||
behaviour](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/concat) | ||
of joining arrays, etc. applies. | ||
Its type signature is: | ||
```haskell | ||
concat :: Promise a -> Promise a -> Promise a | ||
``` | ||
See also [`Promise#conjoin`](#promiseconjoinp) and | ||
[`Promise#append`](#promiseappendp). | ||
### `Promise#chain(f)` | ||
```javascript | ||
var promise = Promise.of(2); | ||
promise.chain(function (x) { return Promise.of(x * 2); }); //=> Promise.of(4) | ||
``` | ||
Execute a function `f` with the value of the promise. This differs from | ||
[`Promise#map`](#promisemapf) in that the function *must* return a promise | ||
itself. | ||
Its type signature is: | ||
```haskell | ||
chain :: Promise a -> (a -> Promise b) -> Promise b | ||
``` | ||
### `Promise#ap(p)` | ||
```javascript | ||
var promise = Promise.of(function (x) { return x * 2; }), | ||
promise2 = Promise.of(2); | ||
promise.ap(promise2); //=> Promise.of(4) | ||
``` | ||
On a promise containing a function, call that function with a promise `p` | ||
containing a value. | ||
Its type signature is: | ||
```haskell | ||
ap :: Promise (a -> b) -> Promise a -> Promise b | ||
``` | ||
### `Promise#empty()` | ||
```javascript | ||
var promise = Promise.of('woo'); | ||
promise.empty(); //=> Promise.of('') | ||
``` | ||
On a promise containing a monoid (viz. something with an `empty()` function on | ||
itself or its constructor like `Array` or `String`), return a new promise with | ||
an empty version of the initial value. | ||
(Pacta ships with Monoid implementations for `Array` and `String` by default.) | ||
### `Promise#conjoin(p)` | ||
```javascript | ||
var promise = Promise.of(1), | ||
promise2 = Promise.of([2, 3]); | ||
promise.conjoin(promise2); //=> Promise.of([1, 2, 3]) | ||
``` | ||
Conjoin the promise with another promise `p`, converting their values to | ||
arrays if needed (e.g. `'foo'` into `['foo']`). This differs from | ||
[`Promise#concat`](#promiseconcatp) which only works on promises of values | ||
that are semigroups themselves. | ||
All values are coerced to arrays using `[].concat`. | ||
### `Promise#append(p)` | ||
```javascript | ||
var promise = Promise.of([]), | ||
promise2 = Promise.of([1]); | ||
promise.append(promise2); //=> Promise.of([[1]]) | ||
``` | ||
On a promise of a list, append another promise `p`'s value to it without | ||
joining (e.g. appending `[1]` to `[]` results in `[[1]]`). | ||
This is particularly useful when dealing with several promises containing | ||
lists and you want to keep them separated instead of being merged into one as | ||
would happen with [`Promise#concat`](#promiseconcatp) and | ||
[`Promise#conjoin`](#promiseconjoinp). | ||
### `Promise#spread(f)` | ||
```javascript | ||
var promise = Promise.of([1, 2]); | ||
promise.spread(function (x, y) { | ||
return x + y; | ||
}); //=> Promise.of(3) | ||
``` | ||
Similar to [`Promise#map`](#promisemapf), apply a function `f` to a promise of | ||
a list but, instead of receiving a single argument, pass each value of the | ||
list to the function separately. | ||
## Acknowledgements | ||
[James | ||
Coglan](http://blog.jcoglan.com/2013/03/30/callbacks-are-imperative-promises-are-functional-nodes-biggest-missed-opportunity/) | ||
and [Aanand Prasad](http://aanandprasad.com/articles/negronis/) convinced me | ||
to explore the idea of monadic promises and [Brian McKenna's "Fantasy Land" | ||
specification](https://github.com/puffnfresh/fantasy-land) and | ||
[feedback](https://github.com/mudge/pacta/issues/1) were essential. | ||
## License | ||
Copyright © 2013 Paul Mucur. | ||
Distributed under the MIT License. | ||
@@ -300,9 +300,9 @@ var assert = require('assert'), | ||
describe('#combine', function () { | ||
it('conjoins promises without flattening lists', function (done) { | ||
describe('#append', function () { | ||
it('appends promises to a promise of an array', function (done) { | ||
var p = Promise.of([1]), | ||
p2 = Promise.of([2, 3]); | ||
p2 = Promise.of(2); | ||
p.combine(p2).map(function (x) { | ||
assert.deepEqual([[1], [2, 3]], x); | ||
p.append(p2).map(function (x) { | ||
assert.deepEqual([1, 2], x); | ||
done(); | ||
@@ -312,5 +312,8 @@ }); | ||
it('conjoins non-list promises', function (done) { | ||
p.combine(p2).map(function (x) { | ||
assert.deepEqual(['foo', 'bar'], x); | ||
it('appends promises of arrays to arrays without joining them', function (done) { | ||
var p = Promise.of([1]), | ||
p2 = Promise.of([2]); | ||
p.append(p2).map(function (x) { | ||
assert.deepEqual([1, [2]], x); | ||
done(); | ||
@@ -320,7 +323,10 @@ }); | ||
it('conjoins both list and non-list promises', function (done) { | ||
var p2 = Promise.of([2, 3]); | ||
it('can be chained without nesting arrays', function (done) { | ||
var p = Promise.of([]), | ||
p2 = Promise.of([1]), | ||
p3 = Promise.of([2, 3]), | ||
p4 = Promise.of([4]); | ||
p.combine(p2).map(function (x) { | ||
assert.deepEqual(['foo', [2, 3]], x); | ||
p.append(p2).append(p3).append(p4).map(function (x) { | ||
assert.deepEqual([[1], [2, 3], [4]], x); | ||
done(); | ||
@@ -331,7 +337,7 @@ }); | ||
describe('#explode', function () { | ||
describe('#spread', function () { | ||
it('calls the given function with each value of the Promise', function (done) { | ||
var p = Promise.of([1, 2, 3]); | ||
p.explode(function (x, y, z) { | ||
p.spread(function (x, y, z) { | ||
assert.equal(1, x); | ||
@@ -347,3 +353,3 @@ assert.equal(2, y); | ||
p.explode(function (x, y, z) { | ||
p.spread(function (x, y, z) { | ||
return x + y + z; | ||
@@ -350,0 +356,0 @@ }).map(function (x) { |
29182
9
496
307