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

Implements Data.Task #50

Merged
merged 31 commits into from
Oct 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5545c5b
(new task future) Adds Task and Future structures
robotlolita Sep 4, 2016
af8b2fa
(feat future) Derives setoid for ExecutionState
robotlolita Sep 5, 2016
a6150a7
(fix future) work around lack of mutual recursion in Node modules
robotlolita Sep 5, 2016
9920206
(fix future) exposes Future in the Folktale.Data module
robotlolita Sep 5, 2016
eab46f7
(fix future) Fixes order of arguments in promise constructor
robotlolita Sep 5, 2016
5588cc4
(test future) adds tests for Deferred
robotlolita Sep 5, 2016
75acaa4
(feat future) Use prefixed fantasy-land names for Future
robotlolita Sep 7, 2016
7a1ed49
(meta) Uses the es2017 Babel preset
robotlolita Sep 25, 2016
865f0b1
(task) Re-organises the previous Task implementation
robotlolita Sep 25, 2016
0ce0f74
(future) Adds fantasy-land methods
robotlolita Sep 25, 2016
87e01db
(re task future) Use ES2015 classes for futures and tasks
robotlolita Sep 26, 2016
0e05473
(new future task) fantasy-land methods for future/task
robotlolita Oct 1, 2016
385aa9b
(fix task) exports task
robotlolita Oct 3, 2016
5cf5917
(fix future) fixes exports for future
robotlolita Oct 3, 2016
829936c
(test future) fixes tests for future
robotlolita Oct 3, 2016
b40d286
(re future) Change matchWith → willMatchWith
robotlolita Oct 8, 2016
8cdb6d0
(test) chain/listen tests
robotlolita Oct 9, 2016
26336c9
(test) uses babel-polyfill for async functions in tests
robotlolita Oct 12, 2016
2144a99
(re fix) Fixes tests given the new test definitions
robotlolita Oct 12, 2016
c91db88
(fix) Fixes Future.apply's incorrect type
robotlolita Oct 12, 2016
0d4ca11
(test) extends environment to return specific structures
robotlolita Oct 12, 2016
2848ee9
(test) Adds missing tests for Future
robotlolita Oct 12, 2016
60ddbcd
(fix task) Adds missing `new` to Task constructor
robotlolita Oct 13, 2016
ece2d15
(fix task) Fixes argument order in Task's bimap
robotlolita Oct 13, 2016
bd1e57d
(test) Adds tests for Task
robotlolita Oct 13, 2016
9c6543e
(re task) Rename matchWith → willMatchWith
robotlolita Oct 13, 2016
4581e2d
(test) Adds tests for Task.willMatchWith and Task.swap
robotlolita Oct 13, 2016
d1d89f2
(fix task) Uses .maybeCancel() for task cancellation to avoid races
robotlolita Oct 14, 2016
883bb42
(test) Adds tests for swap, or, and, run, of, rejected
robotlolita Oct 14, 2016
5687118
(test) Tests for TaskExecution
robotlolita Oct 15, 2016
0a7dd5a
(new) Have side-effecting methods return the receiver instead of Void
robotlolita Oct 15, 2016
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
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"presets": ["es2015"],
"presets": ["es2015", "es2016", "es2017"],
"plugins": [
"transform-function-bind",
"transform-object-rest-spread",
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ clean:
rm -rf core helpers data test/specs test/helpers index.js

test: clean compile compile-test
FOLKTALE_ASSERTIONS=minimal $(mocha) --reporter spec --ui bdd test/specs
FOLKTALE_ASSERTIONS=minimal $(mocha) --require babel-polyfill --reporter spec --ui bdd test/specs

test-browser: compile compile-test
$(karma) start test/karma-local.js
Expand Down
4 changes: 4 additions & 0 deletions ROADMAP.hs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ module Data.Task where
state :: ExecutionState 'failure 'success
listeners :: Array (DeferredListener 'failure 'success)
}
implements Show

-- Deferred.prototype.resolve(value)
(Deferred 'f 's).resolve :: ('s) => Void
Expand Down Expand Up @@ -637,6 +638,9 @@ module Data.Task where
}
implements Functor 'success, Monad 'success, Applicative 'success, Show

-- Future.prototype.listen(listener)
(Future 'f 's).listen :: (DeferredListener 'f 's) => Void

-- Future.prototype.bimap(onFailure, onSuccess)
(Future 'f 's).bimap :: (('f) => 'f2, ('s) => 's2) => Future 'f2 's2

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@
"babel-eslint": "^6.0.2",
"babel-plugin-module-alias": "^1.2.0",
"babel-plugin-transform-assertion-comments": "^0.3.1",
"babel-plugin-transform-async-to-generator": "^6.8.0",
"babel-plugin-transform-function-bind": "^6.3.13",
"babel-plugin-transform-metamagical-comments": "^0.12.0",
"babel-plugin-transform-object-rest-spread": "^6.3.13",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-es2016": "^6.11.3",
"babel-preset-es2017": "^6.14.0",
"babel-runtime": "^6.11.6",
"browserify": "^13.1.0",
"es5-shim": "^4.5.9",
Expand Down
51 changes: 51 additions & 0 deletions src/data/future/_execution-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//---------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//---------------------------------------------------------------------

// --[ Dependencies ]--------------------------------------------------
const { data, show, setoid } = require('folktale/core/adt');


// --[ Implementation ]------------------------------------------------

/*~
* Describes the current state of a task execution/deferred/future.
*/
const ExecutionState = data('folktale:ExecutionState', {
/*~
* Not yet resolved.
*/
Pending() {
return {};
},

/*~
* Resolved as cancelled.
*/
Cancelled() {
return {};
},

/*~
* Resolved successfully, with a value.
*/
Resolved(value) {
return { value };
},

/*~
* Resolved as a failure, with a reason.
*/
Rejected(reason) {
return { reason };
}
}).derive(show, setoid);


// --[ Exports ]-------------------------------------------------------
module.exports = ExecutionState;
283 changes: 283 additions & 0 deletions src/data/future/_future.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
//---------------------------------------------------------------------
//
// This source file is part of the Folktale project.
//
// See LICENCE for licence information.
// See CONTRIBUTORS for the list of contributors to the project.
//
//---------------------------------------------------------------------

// --[ Dependencies ]--------------------------------------------------
const define = require('folktale/helpers/define');
const provideAliases = require('folktale/helpers/provide-fantasy-land-aliases');
const Deferred = require('./deferred');
const { Pending, Resolved, Rejected } = require('./_execution-state');



// --[ Implementation ]------------------------------------------------

/*~
* Represents an eventual value, like Promise, but without a recursive
* `.then`.
*/
class Future {
constructor() {
define(this, '_state', Pending());
define(this, '_listeners', []);
}


// ---[ State and configuration ]------------------------------------
/*~
* The current state of the future.
*
* ---
* isRequired: true
* category: State and configuration
* type: |
* get (Future 'f 's) => ExecutionState 'f 's
*/
get _state() {
throw new TypeError('Future.prototype._state should be implemented in an inherited object.');
}

/*~
* A list of listeners to notify when the future is resolved.
*
* ---
* isRequired: true
* category: State and configuration
* type: |
* get (Future 'f 's) => Array (DeferredListener 'f 's)
*/
get _listeners() {
throw new TypeError('Future.prototype._listeners should be implemented in an inherited object.');
}


// ---[ Reacting to Future events ]----------------------------------
/*~
* Attaches a listener to be invoked when the Future resolves.
*
* ---
* category: Reacting to Future events
* type: |
* (Future 'f 's).(DeferredListener 'f 's) => Future 'f 's
*/
listen(pattern) {
this._state.matchWith({
Pending: () => this._listeners.push(pattern),
Cancelled: () => pattern.onCancelled(),
Resolved: ({ value }) => pattern.onResolved(value),
Rejected: ({ reason }) => pattern.onRejected(reason)
});
return this;
}


// --[ Transforming Futures ]----------------------------------------
/*~
* Transforms the value inside a future with a monadic function.
*
* ---
* category: Transforming futures
* type: |
* (Future 'f 's).(('s) => Future 's2) => Future 'f 's2
*/
chain(transformation) {
let deferred = new Deferred();
this.listen({
onCancelled: () => deferred.cancel(),
onRejected: reason => deferred.reject(reason),
onResolved: value => {
transformation(value).listen({
onCancelled: () => deferred.cancel(),
onRejected: reason => deferred.reject(reason),
onResolved: value2 => deferred.resolve(value2)
});
}
});

return deferred.future();
}

/*~
* Transforms the value inside a future with a simple function.
*
* ---
* category: Transforming futures
* type: |
* (Future 'f 's).(('s) => 's2) => Future 'f 's2
*/
map(transformation) {
return this.chain(value => Future.of(transformation(value)));
}

/*~
* Transforms the value inside a future with a function contained in
* another future.
*
* ---
* category: Transforming futures
* type: |
* (Future 'f 's).(Future 'f (('s) => 's2)) => Future 'f 's2
*/
apply(future) {
return this.chain(fn => future.map(fn));
}

/*~
* Transforms successes and failures in a future.
*
* ---
* category: Transforming futures
* type: |
* (Future 'f 's).(('f) => 'f2, ('s) => 's2) => Future 'f2 's2
*/
bimap(rejectionTransformation, successTransformation) {
let deferred = new Deferred();
this.listen({
onCancelled: () => deferred.cancel(),
onRejected: reason => deferred.reject(rejectionTransformation(reason)),
onResolved: value => deferred.resolve(successTransformation(value))
});

return deferred.future();
}

/*~
* Transform the values of rejected futures.
*
* ---
* category: Transforming values
* type: |
* (Future 'f 's).(('f) => 'f2) => Future 'f2 's
*/
mapRejection(transformation) {
return this.bimap(transformation, x => x);
}


// ---[ Recovering from errors ]-------------------------------------
/*~
* Transforms a rejected future into a new future.
*
* ---
* category: Recovering from errors
* type: |
* (Future 'f 's).(('f) => Future 'f2 's2) => Future 'f2 's
*/
recover(handler) {
let deferred = new Deferred();
this.listen({
onCancelled: () => deferred.cancel(),
onResolved: value => deferred.resolve(value),
onRejected: reason => {
handler(reason).listen({
onCancelled: () => deferred.cancel(),
onResolved: value => deferred.resolve(value),
onRejected: newReason => deferred.reject(newReason)
});
}
});

return deferred.future();
}

willMatchWith(pattern) {
let deferred = new Deferred();
const resolve = (handler) => (value) => handler(value).listen({
onCancelled: () => deferred.cancel(),
onResolved: (value) => deferred.resolve(value),
onRejected: (reason) => deferred.reject(reason)
});
this.listen({
onCancelled: resolve(pattern.Cancelled),
onResolved: resolve(pattern.Resolved),
onRejected: resolve(pattern.Rejected)
});

return deferred.future();
}

/*~
* Transforms rejected futures in successes, and vice-versa.
*
* ---
* category: Recovering from errors
* type: |
* (Future 'f 's).() => Future 's 'f
*/
swap() {
let deferred = new Deferred();
this.listen({
onCancelled: () => deferred.cancel(),
onRejected: reason => deferred.resolve(reason),
onResolved: value => deferred.reject(value)
});

return deferred.future();
}


// ---[ Debugging ]--------------------------------------------------
/*~
* Returns a textual representation of this object for debugging.
*
* ---
* category: Debugging
* type: |
* (Future 'f 's).() => String
*/
toString() {
const listeners = this._listeners.length;
const state = this._state;

return `folktale:Future(${state}, ${listeners} listeners)`;
}

/*~
* Returns a textual representation of this object for Node's REPL.
*
* ---
* category: Debugging
* type: |
* (Future 'f 's).() => String
*/
inspect() {
return this.toString();
}
}


// ---[ Constructing futures ]-----------------------------------------
Object.assign(Future, {
of(value) {
let result = new Future();
result._state = Resolved(value);
return result;
},

/*~
* Constructs a future containing a single rejected value.
*
* ---
* category: Constructing futures
* type: |
* (Future).('f) => Future 'f 's
*/
rejected(reason) {
let result = new Future();
result._state = Rejected(reason);
return result;
}
});


provideAliases(Future);
provideAliases(Future.prototype);


// --[ Exports ]-------------------------------------------------------
module.exports = Future;
Loading