Subversion Repositories Applications.papyrus

Rev

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 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();
})();

}