if (!dojo._hasResource["dojox.lang.functional"]) { // _hasResource checks added // by build. Do not use // _hasResource directly in // your code. dojo._hasResource["dojox.lang.functional"] = true; dojo.provide("dojox.lang.functional"); // This module adds high-level functions and related constructs: // - list comprehensions similar to JavaScript 1.7 // - anonymous functions built from the string // - zip combiners // - "reduce" family of functions // - currying and partial functions // - argument pre-processing: mixer and flip // - miscellaneous useful functions // Acknoledgements: // - parts of this module (most notably lambda, constFun, invoke, pluck, and // partial) // are based on work by Oliver Steele // (http://osteele.com/sources/javascript/functional/functional.js) // which was published under MIT License // - Simple "maybe" monad was donated by Alex Russell. // Notes: // - Dojo provides following high-level functions in dojo/_base/array.js: // forEach, map, filter, every, some // - These functions implemented with optional lambda expression as a // parameter. // - missing high-level functions are provided with the compatible API: // foldl, foldl1, scanl, scanl1, foldr, foldr1, scanr, scanr1, // reduce, reduceRight // - lambda() and listcomp() produce functions, which after the compilation // step are // as fast as regular JS functions (at least theoretically). (function() { var d = dojo, df = dojox.lang.functional, g_re = /\bfor\b|\bif\b/gm, empty = {}; // split() is augmented on IE6 to ensure the uniform behavior var split = "ab".split(/a*/).length > 1 ? String.prototype.split : function(sep) { var r = this.split.call(this, sep), m = sep.exec(this); if (m && m.index == 0) { r.unshift(""); } return r; }; var lambda = function(/* String */s) { var args = [], sects = split.call(s, /\s*->\s*/m); if (sects.length > 1) { while (sects.length) { s = sects.pop(); args = sects.pop().split(/\s*,\s*|\s+/m); if (sects.length) { sects.push("(function(" + args + "){return (" + s + ")})"); } } } else if (s.match(/\b_\b/)) { args = ["_"]; } else { var l = s.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), r = s .match(/[+\-*\/%&|\^\.=<>!]\s*$/m); if (l || r) { if (l) { args.push("$1"); s = "$1" + s; } if (r) { args.push("$2"); s = s + "$2"; } } else { var vars = s .replace( /(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|this|true|false|null|undefined|typeof|instanceof|in|delete|new|void|arguments|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|escape|eval|isFinite|isNaN|parseFloat|parseInt|unescape|dojo|dijit|dojox|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, "").match(/([a-z_$][a-z_$\d]*)/gi) || []; var t = {}; d.forEach(vars, function(v) { if (!(v in t)) { args.push(v); t[v] = 1; } }); } } return { args : args, body : "return (" + s + ");" }; // Object }; var listcomp = function(/* String */s) { var frag = s.split(g_re), act = s.match(g_re), head = ["var r = [];"], tail = []; for (var i = 0; i < act.length;) { var a = act[i], f = frag[++i]; if (a == "for" && !/^\s*\(\s*(;|var)/.test(f)) { f = f.replace(/^\s*\(/, "(var "); } head.push(a, f, "{"); tail.push("}"); } return head.join("") + "r.push(" + frag[0] + ");" + tail.join("") + "return r;"; // String }; var currying = function(/* Object */info) { return function() { // Function if (arguments.length + info.args.length < info.arity) { return currying({ func : info.func, arity : info.arity, args : Array.prototype.concat.apply(info.args, arguments) }); } return info.func.apply(this, Array.prototype.concat.apply( info.args, arguments)); }; }; var identity = function(x) { return x; }; var compose = function(/* Array */a) { return a.length ? function() { var i = a.length - 1, x = df.lambda(a[i]) .apply(this, arguments); for (--i; i >= 0; --i) { x = df.lambda(a[i]).call(this, x); } return x; } : identity; }; d.mixin(df, { // lambda buildLambda : function(/* String */s) { // summary: builds a function from a snippet, returns a string, // which represents the function. // description: This method returns a textual representation of // a function // built from the snippet. It is meant to be evaled in the // proper context, // so local variables can be pulled from the environment. s = lambda(s); return "function(" + s.args.join(",") + "){" + s.body + "}"; // String }, lambda : function(/* Function|String|Array */s) { // summary: builds a function from a snippet, or array // (composing), returns // a function object; functions are passed through unmodified. // description: This method is used to normalize a functional // representation // (a text snippet, an array, or a function) to a function // object. if (typeof s == "function") { return s; } if (s instanceof Array) { return compose(s); } s = lambda(s); return new Function(s.args, s.body); // Function }, // sequence generators repeat : function(/* Number */n, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: builds an array by repeatedly applying a unary // function N times // with a seed value Z. o = o || d.global; f = df.lambda(f); var t = new Array(n); t[0] = z; for (var i = 1; i < n; t[i] = z = f.call(o, z), ++i); return t; // Array }, until : function(/* Function|String|Array */pr, /* Function|String|Array */ f, /* Object */z, /* Object? */o) { // summary: builds an array by repeatedly applying a unary // function with // a seed value Z until the predicate is satisfied. o = o || d.global; f = df.lambda(f); pr = df.lambda(pr); var t = []; for (; !pr.call(o, z); t.push(z), z = f.call(o, z)); return t; // Array }, buildListcomp : function(/* String */s) { // summary: builds a function from a text snippet, which // represents a valid // JS 1.7 list comprehension, returns a string, which represents // the function. // description: This method returns a textual representation of // a function // built from the list comprehension text snippet (conformant to // JS 1.7). // It is meant to be evaled in the proper context, so local // variable can be // pulled from the environment. return "function(){" + listcomp(s) + "}"; // String }, compileListcomp : function(/* String */s) { // summary: builds a function from a text snippet, which // represents a valid // JS 1.7 list comprehension, returns a function object. // description: This method returns a function built from the // list // comprehension text snippet (conformant to JS 1.7). It is // meant to be // reused several times. return new Function([], listcomp(s)); // Function }, listcomp : function(/* String */s) { // summary: executes the list comprehension building an array. return (new Function([], listcomp(s)))(); // Array }, // classic reduce-class functions foldl : function(/* Array */a, /* Function */f, /* Object */z, /* Object? */ o) { // summary: repeatedly applies a binary function to an array // from left // to right using a seed value as a starting point; returns the // final // value. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); for (var i = 0; i < a.length; z = f.call(o, z, a[i], i, a), ++i); return z; // Object }, foldl1 : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: repeatedly applies a binary function to an array // from left // to right; returns the final value. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var z = a[0]; for (var i = 1; i < a.length; z = f.call(o, z, a[i], i, a), ++i); return z; // Object }, scanl : function(/* Array */a, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: repeatedly applies a binary function to an array // from left // to right using a seed value as a starting point; returns an // array // of values produced by foldl() at that point. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = new Array(n + 1); t[0] = z; for (var i = 0; i < n; z = f.call(o, z, a[i], i, a), t[++i] = z); return t; // Array }, scanl1 : function(/* Array */a, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: repeatedly applies a binary function to an array // from left // to right; returns an array of values produced by foldl1() at // that // point. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = new Array(n), z = a[0]; t[0] = z; for (var i = 1; i < n; z = f.call(o, z, a[i], i, a), t[i++] = z); return t; // Array }, foldr : function(/* Array */a, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: repeatedly applies a binary function to an array // from right // to left using a seed value as a starting point; returns the // final // value. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); for (var i = a.length; i > 0; --i, z = f.call(o, z, a[i], i, a)); return z; // Object }, foldr1 : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: repeatedly applies a binary function to an array // from right // to left; returns the final value. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, z = a[n - 1]; for (var i = n - 1; i > 0; --i, z = f.call(o, z, a[i], i, a)); return z; // Object }, scanr : function(/* Array */a, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: repeatedly applies a binary function to an array // from right // to left using a seed value as a starting point; returns an // array // of values produced by foldr() at that point. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = new Array(n + 1); t[n] = z; for (var i = n; i > 0; --i, z = f.call(o, z, a[i], i, a), t[i] = z); return t; // Array }, scanr1 : function(/* Array */a, /* Function|String|Array */f, /* Object */ z, /* Object? */o) { // summary: repeatedly applies a binary function to an array // from right // to left; returns an array of values produced by foldr1() at // that // point. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = new Array(n), z = a[n - 1]; t[n - 1] = z; for (var i = n - 1; i > 0; --i, z = f.call(o, z, a[i], i, a), t[i] = z); return t; // Array }, // JS 1.6 standard array functions, which can take a lambda as a // parameter. // Consider using dojo._base.array functions, if you don't need the // lambda support. filter : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: creates a new array with all elements that pass the // test // implemented by the provided function. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = [], v; for (var i = 0; i < n; ++i) { v = a[i]; if (f.call(o, v, i, a)) { t.push(v); } } return t; // Array }, forEach : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: executes a provided function once per array element. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length; for (var i = 0; i < n; f.call(o, a[i], i, a), ++i); }, map : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: creates a new array with the results of calling // a provided function on every element in this array. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length, t = new Array(n); for (var i = 0; i < n; t[i] = f.call(o, a[i], i, a), ++i); return t; // Array }, every : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: tests whether all elements in the array pass the // test // implemented by the provided function. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length; for (var i = 0; i < n; ++i) { if (!f.call(o, a[i], i, a)) { return false; // Boolean } } return true; // Boolean }, some : function(/* Array */a, /* Function|String|Array */f, /* Object? */ o) { // summary: tests whether some element in the array passes the // test // implemented by the provided function. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); var n = a.length; for (var i = 0; i < n; ++i) { if (f.call(o, a[i], i, a)) { return true; // Boolean } } return false; // Boolean }, // JS 1.8 standard array functions, which can take a lambda as a // parameter. reduce : function(/* Array */a, /* Function */f, /* Object? */z) { // summary: apply a function simultaneously against two values // of the array // (from left-to-right) as to reduce it to a single value. return arguments.length < 3 ? df.foldl1(a, f) : df.foldl(a, f, z); // Object }, reduceRight : function(/* Array */a, /* Function */f, /* Object? */z) { // summary: apply a function simultaneously against two values // of the array // (from right-to-left) as to reduce it to a single value. return arguments.length < 3 ? df.foldr1(a, f) : df.foldr(a, f, z); // Object }, // currying and partial functions curry : function(/* Function|String|Array */f, /* Number? */arity) { // summary: curries a function until the arity is satisfied, at // which point it returns the calculated value. f = df.lambda(f); arity = typeof arity == "number" ? arity : f.length; return currying({ func : f, arity : arity, args : [] }); // Function }, arg : {}, // marker for missing arguments partial : function(/* Function|String|Array */f) { // summary: creates a function where some arguments are bound, // and // some arguments (marked as dojox.lang.functional.arg) are will // be // accepted by the final function in the order they are // encountered. // description: This method is used to produce partially bound // functions. If you want to change the order of arguments, use // dojox.lang.functional.mixer() or // dojox.lang.functional.flip(). var a = arguments, args = new Array(a.length - 1), p = []; f = df.lambda(f); for (var i = 1; i < a.length; ++i) { var t = a[i]; args[i - 1] = t; if (t == df.arg) { p.push(i - 1); } } return function() { // Function var t = Array.prototype.slice.call(args, 0); // clone the // array for (var i = 0; i < p.length; ++i) { t[p[i]] = arguments[i]; } return f.apply(this, t); }; }, // argument pre-processing mixer : function(/* Function|String|Array */f, /* Array */mix) { // summary: changes the order of arguments using an array of // numbers mix --- i-th argument comes from mix[i]-th place // of supplied arguments. f = df.lambda(f); return function() { // Function var t = new Array(mix.length); for (var i = 0; i < mix.length; ++i) { t[i] = arguments[mix[i]]; } return f.apply(this, t); }; }, flip : function(/* Function|String|Array */f) { // summary: changes the order of arguments by reversing their // order. f = df.lambda(f); return function() { // Function // reverse arguments var a = arguments, l = a.length - 1, t = new Array(l + 1), i; for (i = 0; i <= l; ++i) { t[l - i] = a[i]; } return f.apply(this, t); }; }, // combiners zip : function() { // summary: returns an array of arrays, where the i-th array // contains the i-th element from each of the argument arrays. // description: This is the venerable zip combiner (for example, // see Python documentation for general details). The returned // array is truncated to match the length of the shortest input // array. var n = arguments[0].length, m = arguments.length, i; for (i = 1; i < m; n = Math.min(n, arguments[i++].length)); var t = new Array(n), j; for (i = 0; i < n; ++i) { var p = new Array(m); for (j = 0; j < m; p[j] = arguments[j][i], ++j); t[i] = p; } return t; // Array }, unzip : function(/* Array */a) { // summary: similar to dojox.lang.functional.zip(), but takes // a single array of arrays as the input. // description: This function is similar to // dojox.lang.functional.zip() // and can be used to unzip objects packed by // dojox.lang.functional.zip(). It is here mostly to provide // a short-cut for the different method signature. return df.zip.apply(null, a); // Array }, // miscelaneous functional adapters constFun : function(/* Object */x) { // summary: returns a function, which produces a constant value // regardless of supplied parameters. return function() { return x; }; // Function }, invoke : function(/* String */m) { // summary: returns a function, which invokes a method on // supplied // object using optional parameters. return function(/* Object */o) { // Function return o[m].apply(o, Array.prototype.slice.call(arguments, 1)); }; }, pluck : function(/* String */m) { // summary: returns a function, which returns a named object // member. return function(/* Object */o) { // Function return o[m]; }; }, // object helpers forIn : function(/* Object */obj, /* Function|String|Array */f, /* Object? */ o) { // summary: iterates over all object members skipping members, // which // are present in the empty object (IE and/or 3rd-party // libraries). o = o || d.global; f = df.lambda(f); for (var i in obj) { if (i in empty) { continue; } f.call(o, obj[i], i, obj); } }, forEachReversed : function(/* Array */a, /* Function|String|Array */ f, /* Object? */o) { // summary: executes a provided function once per array element. a = typeof a == "string" ? a.split("") : a; o = o || d.global; f = df.lambda(f); for (var i = a.length - 1; i >= 0; f.call(o, a[i], i, a), --i); } }); // monads dojo.declare("dojox.lang.functional.MaybeMonad", null, { constructor : function(/* Object */value) { // summary: constructs a monad optionally initializing // all additional members if (arguments.length) { this.value = value; } }, bind : function(/* dojox.lang.functional.Monad */monad, /* Function|String|Array */ f, /* Object? */o) { // summary: this is the classic bind method, which // applies a function to a monad, // and returns a result as a monad; it is meant to be // overwritten to incorporate // side effects if (!("value" in monad)) { return new this.constructor(); // dojox.lang.functional.MaybeMonad } // => possible side-effects go here o = o || d.global; f = df.lambda(f); return f.call(o, monad.value); // dojox.lang.functional.Monad }, // class-specific methods isNothing : function() { // summary: check if there is no bound value. return !("value" in this); // Boolean } }); df.MaybeMonad.returnMonad = function(/* Object */value) { // summary: puts a valye in the Maybe monad. return new df.MaybeMonad(value); // dojox.lang.functional.MaybeMonad }; df.MaybeMonad.zero = new df.MaybeMonad(); })(); }