New file |
0,0 → 1,462 |
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(); |
})(); |
|
} |