Blame | Last modification | View Log | RSS feed
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 behaviorvar 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(){ // Functionif(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, {// lambdabuildLambda: 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 generatorsrepeat: 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 functionsfoldl: 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 functionscurry: 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 argumentspartial: 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(){ // Functionvar t = Array.prototype.slice.call(args, 0); // clone the arrayfor(var i = 0; i < p.length; ++i){t[p[i]] = arguments[i];}return f.apply(this, t);};},// argument pre-processingmixer: 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(){ // Functionvar 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 argumentsvar 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);};},// combinerszip: 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 adaptersconstFun: 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){ // Functionreturn 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){ // Functionreturn o[m];};},// object helpersforIn: 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);}});// monadsdojo.declare("dojox.lang.functional.MaybeMonad", null, {constructor: function(/*Object*/ value){// summary: constructs a monad optionally initializing all additional membersif(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 effectsif(!("value" in monad)){return new this.constructor(); // dojox.lang.functional.MaybeMonad}// => possible side-effects go hereo = o || d.global; f = df.lambda(f);return f.call(o, monad.value); // dojox.lang.functional.Monad},// class-specific methodsisNothing: 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();})();}