Permalink
Browse files

First commit

  • Loading branch information...
0 parents commit 5d5b3ea22bcdda56caf00f4eedddd2c0bfec4873 @douglascrockford committed Oct 5, 2012
Showing with 376 additions and 0 deletions.
  1. +7 −0 README
  2. +113 −0 monad.js
  3. +256 −0 vow.js
7 README
@@ -0,0 +1,7 @@
+This is the code companion to my one act, one man show,
+_Monads & Gonads_.
+
+It contains two JavaScript files:
+
+ monad.js for making monads
+ vow.js for making promises
113 monad.js
@@ -0,0 +1,113 @@
+// monad.js
+// Douglas Crockford
+// 2012-10-04
+
+// Public Domain
+
+// The MONAD function is a factory that produces monad constructor functions.
+// It can take an optional modifier function, which is a function that is
+// allowed to modify new monads at the end of the construction processes.
+
+// A monad constructor (sometimes called 'unit' or 'return' in some mythologies)
+// comes with three methods, lift, lift_value, and method, which can add
+// methods and properties to the monad's prototype.
+
+// A monad has a 'bind' method that takes a function that receives a value and
+// is expected to return a monad.
+
+// var unit = MONAD();
+// var monad = unit("Hello world.");
+// monad.bind(alert);
+
+// var ajax = MONAD()
+// .lift('alert', alert);
+// var monad = ajax("Hello world.");
+// monad.bind(alert);
+// monad.alert();
+
+// var maybe = MONAD(function (monad, value) {
+// if (value === null || value === undefined) {
+// monad.is_null = true;
+// monad.bind = function () {
+// return monad;
+// };
+// }
+// });
+// var monad = maybe(null);
+// monad.bind(alert);
+
+function MONAD(modifier) {
+ 'use strict';
+
+// Each construct has a monad prototype. The prototype will contain an
+// is_monad property for classification.
+
+ var prototype = Object.create(null);
+ prototype.is_monad = true;
+
+// Each call to MONAD will produce a new unit function.
+
+ function unit(value) {
+
+// Construct a new monad.
+
+ var monad = Object.create(prototype);
+
+// Add a bind method that will deliver the unit's value parameter to a
+// function/
+
+ monad.bind = function (func, args) {
+
+// bind takes a function and an optional array of arguments. It calls that
+// function passing the monad's value and bind's optional array of args.
+
+// With ES6, this horrible statement can be replaced with
+
+// return func(value, ...args);
+
+ return func.apply(undefined,
+ [value].concat(Array.prototype.slice.apply(args || [])));
+ };
+
+// If MONAD's modifier parameter is a function, then call it, passing the monad
+// and the value.
+
+ if (typeof modifier === 'function') {
+ modifier(monad, value);
+ }
+
+// Return the shiny new monad.
+
+ return monad;
+ }
+ unit.method = function (name, func) {
+
+// Add a method to the prototype.
+
+ prototype[name] = func;
+ return unit;
+ };
+ unit.lift_value = function (name, func) {
+
+// Add a method to the prototype that calls bind with the func. This can be
+// used for ajax methods that return values other than monads.
+
+ prototype[name] = function () {
+ return this.bind(func, arguments);
+ };
+ return unit;
+ };
+ unit.lift = function (name, func) {
+
+// Add a method to the prototye that calls bind with the func. If the value
+// returned by the func is not a monad, then make a monad.
+
+ prototype[name] = function () {
+ var result = this.bind(func, arguments);
+ return result.is_monad === true ? result : unit(result);
+ };
+ return unit;
+ };
+ return unit;
+}
+
256 vow.js
@@ -0,0 +1,256 @@
+// vow.js
+// Douglas Crockford
+// 2012-10-04
+
+// Public Domain
+
+/*jslint es5: true */
+
+var setTimeout, setImmediate;
+
+if (typeof setImmediate !== 'function') {
+ setImmediate = function setImmediate(func, param) {
+ 'use strict';
+ return setTimeout(function () {
+ func(param);
+ }, 0);
+ };
+}
+
+
+var VOW = (function () {
+ 'use strict';
+
+// The VOW object contains a .make function that is used to make vows.
+// It may also contain other useful functions.
+// In some mythologies, vow is called defer.
+
+
+ function enqueue(
+ queue, // An array of resolve functions
+ func, // A function that was registered with the .when method
+ resolver, // A resolve function to append to the queue
+ breaker // A break resolve function to be used if func fails
+ ) {
+
+// enqueue is a helper function used by .when. It will append a resolution to
+// either the keepers queue or the breakers queue. If func is a function then
+// push a new resolution function that will attempt to pass the result of the
+// func to the resolver function; if that fails, then call the breaker function
+// to recover. If func is not a function (which could happen if .when was called
+// with missing arguments) then push the resolver function itself, which has the
+// effect of passing the value down to the next .when, if the whens are chained.
+// If the result of func is a promise, then the resolver or breaker will be
+// called when that promise resolves.
+
+ queue[queue.length] = typeof func === 'function'
+ ? function (value) {
+ try {
+ var result = func(value);
+ if (resolver(result.is_promise !== true)) {
+ resolver(result);
+ } else {
+ result.when(resolver, breaker);
+ }
+ } catch (e) {
+ breaker(e);
+ }
+ }
+ : resolver;
+ }
+
+ function enlighten(queue, fate) {
+
+// enlighten is a helper function of herald and .when. It schedules the
+// processing of all of the resolution functions in either the keepers queue
+// or the breakers queue in later turns with the promise's fate.
+
+ queue.forEach(function (func) {
+ setImmediate(func, fate);
+ });
+ }
+
+ return {
+ make: function make() {
+
+// The make function makes new vows. A vow contains a promise object and the
+// two resolution functions: break and keep that determine the fate of the
+// promise.
+
+ var breakers = [], // .when's broken queue
+ fate, // The promise's ultimate value
+ keepers = [], // .when's kept queue
+ status = 'pending'; // 'broken', 'kept', or 'pending'
+
+ function herald(state, value, queue) {
+
+// The herald function is a helper function of break and keep.
+// It seals the promise's fate, updates its status, enlightens one of the
+// queues, and empties both queues.
+
+ if (status !== 'pending') {
+ throw "overpromise";
+ }
+ fate = value;
+ status = state;
+ enlighten(queue, fate);
+ keepers.length = 0;
+ breakers.length = 0;
+ }
+
+// Construct and return the vow object.
+
+ return {
+ break: function (value) {
+
+// The break method breaks the promise.
+
+ herald('broken', value, breakers);
+ },
+ keep: function keep(value) {
+
+// The keep method keeps the promise.
+
+ herald('kept', value, keepers);
+ },
+ promise: {
+ is_promise: true,
+
+// The .when method is the promise monad's bind. The .when method can take two
+// optional functions. One of those functions will be called, depending on the
+// promise's resolution.
+
+ when: function (kept, broken) {
+
+// Make a new vow. The promise will be the return value.
+
+ var vow = make();
+ switch (status) {
+
+// If the promise is still pending, then enqueue both arguments.
+
+ case 'pending':
+ enqueue(keepers, kept, vow.keep, vow.break);
+ enqueue(breakers, broken, vow.break, vow.break);
+ break;
+
+// If the promise has already been kept, then enqueue only the kept function,
+// and enlighten.
+
+ case 'kept':
+ enqueue(keepers, kept, vow.keep, vow.break);
+ enlighten(keepers, fate);
+ break;
+
+// If the promise has already been broken, then enqueue only the broken
+// function, and enlighten.
+
+ case 'broken':
+ enqueue(breakers, broken, vow.break, vow.break);
+ enlighten(breakers, fate);
+ break;
+ }
+ return vow.promise;
+ }
+ }
+ };
+ },
+ every: function every(array) {
+
+// The every function takes an array of promises and returns a promise that
+// will deliver an array of results only if every promise is kept.
+
+ var remaining = array.length, results = [], vow = VOW.make();
+
+ if (remaining === 0) {
+ vow.keep(results);
+ } else {
+ array.forEach(function (promise, i) {
+ promise.when(function (value) {
+ results[i] = value;
+ remaining -= 1;
+ if (remaining === 0) {
+ vow.keep(results);
+ }
+ }, function (reason) {
+ remaining = NaN;
+ vow.break(reason);
+ });
+ });
+ }
+ return vow.promise;
+ },
+ first: function every(array) {
+
+// The first function takes an array of promises and returns a promise to
+// deliver the first observed result, or a broken promise if none are kept.
+
+ var found = false, remaining = array.length, vow = VOW.make();
+
+ function check() {
+ remaining -= 1;
+ if (remaining === 0 && !found) {
+ vow.break();
+ }
+ }
+
+ if (remaining === 0) {
+ vow.break('first');
+ } else {
+ array.forEach(function (promise) {
+ promise.when(function (value) {
+ if (!found) {
+ found = true;
+ vow.keep(value);
+ }
+ check();
+ }, check);
+ });
+ }
+ return vow.promise;
+ },
+ any: function any(array) {
+
+// The any function takes an array of promises and returns a promise that
+// will deliver a possibly sparse array of results of any kept promises.
+// The result will contain an undefined cell for each broken promise.
+
+ var remaining = array.length, results = [], vow = VOW.make();
+
+ function check() {
+ remaining -= 1;
+ if (remaining === 0) {
+ vow.keep(results);
+ }
+ }
+
+ if (remaining === 0) {
+ vow.keep(results);
+ } else {
+ array.forEach(function (promise, i) {
+ promise.when(function (value) {
+ results[i] = value;
+ check();
+ }, check);
+ });
+ }
+ return vow.promise;
+ },
+ kept: function (value) {
+
+// Returns a new kept promise.
+
+ var vow = VOW.make();
+ vow.keep(value);
+ return vow.promise;
+ },
+ broken: function (reason) {
+
+// Returns a new broken promise/
+
+ var vow = VOW.make();
+ vow.break(reason);
+ return vow.promise;
+ }
+ };
+}());

0 comments on commit 5d5b3ea

Please sign in to comment.