Skip to content

Instantly share code, notes, and snippets.

@robotlolita
Last active January 1, 2016 01:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robotlolita/8072871 to your computer and use it in GitHub Desktop.
Save robotlolita/8072871 to your computer and use it in GitHub Desktop.
Monadic Futures specification

A future should provide the fork, of, chain, and orElse methods, subject to the following rules:

new Future(computation)

  1. The computation should fulfil the type (α → γ), (β → γ) → γ.
  2. The value should be stored in the internal field [[Computation]], as-is.
  3. If computation is not a function, the behaviour is undefined.

Future.prototype.fork(f, g)

  1. Should invoke the [[Computation]] passing f as the first argument, and g as the second argument.
  2. Should return whatever [[Computation]] returns.

Future.of(a)

  1. The of method must return a Future containing a.
  2. No parts of a should be checked.

Future.prototype.chain(f)

  1. f must be an unary function that returns a Future.
  2. f will be called with the successful value of the Future, if the Future contains a successful value.
  3. If the future contains a successful value, chain must return a future equivalent to the one returned by applying f to the successful value.
  4. If the future contains an error value, chain must return a future equivalent to itself.

Future.prototype.orElse(f)

  1. f must be an unary function that returns a Future.
  2. f will be called with the error value of the Future, if the Future contains an error.
  3. If the Future contains a successful value, orElse must return a Future equivalent to itself.
  4. If the Future contains an error value, orElse must return a Future equivalent to the one returned by applying f to the error value.

Laws

Furthermore, the previous methods should satisfy the following equivalences:

  • p.chain(f).chain(g) = p.chain(x => f(x).chain(g)) — associativity
  • p.of(a).chain(f) = f(a) — left identity
  • p.chain(p.of) = p — right identity

Interface

type Future[α, β] where
  new    :: ((α  γ), (β  γ)  γ)  Future[α, β]
  fork   :: @Future[α, β] => (α  γ), (β  γ)  γ
  of     :: β  Future[α, β]
  chain  :: (@Future[α, β]) => (β  Future[α, γ])  Future[α, γ]
  orElse :: (@Future[α, β]) => (α  Future[γ, β])  Future[γ, β]
function Future(computation) {
this.fork = computation
}
// Puts *any* value inside of a future
Future.of = function(value) {
return new Future(function(resolve, reject) {
resolve(value)
})
}
// Runs a computation over the successful value of a future. The computation
// must return a new future.
Future.prototype.chain = function(computation) {
return new Future(function(resolve, reject) {
// We run the future
this.fork(
// If the future returns a value, we run the computation over this
// value, and propagate the state of the resulting future to the
// one we've returned
function(value) {
computation(value).fork(resolve, reject)
// If the future doesn't return a value, we just propagate the error.
}, function(error) {
reject(error)
}
)
}.bind(this))
}
// Runs a computation error value of a future. The computation must return a new future.
Future.prototype.orElse = function(computation) {
return new Future(function(resolve, reject) {
this.fork(function(value){ resolve(value) }
,function(error){ computation(error).fork(resolve, reject) })
})
}
var fs = require('fs')
function read(path) {
return new Future(function(resolve, reject) {
fs.readFile(path, function(err, data) {
if (err) reject(err)
else resolve(data)
})
})
}
read('./foo.txt').chain(function(a) {
return read('./bar.txt').chain(function(b) {
return Future.of(a + '\n' + b)
})
}).orElse(function(err) {
console.log(err)
}).fork(x => x, y => y)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment