Subversion Repositories wimsdev

Rev

Blame | Last modification | View Log | RSS feed

  1. /*  jscurry.js:  a JavaScript module for functional programming; requires ECMAScript 3.  These
  2.         definitions are based on Haskell's, but allow side effects, and do not use automatic lazy
  3.         evaluation, compile-time type checking, or automatic Currying.
  4.        
  5.         We believe that "member functions" are the wrong technique in general for implementing
  6.         function closures or passing functions to polymorphic algorithms.
  7.        
  8.         Suffixes (in this and other modules):
  9.                 F:  function
  10.                 P ("Possible" or "Pointer"):  null/undefined treated specially
  11.                 Q ("Question"):  value effectively converted to a boolean when used
  12.                 S:  string
  13.                 _:  a non-constant variable with a large scope
  14.                 $:  a jQuery() result or "wrapped set"
  15.        
  16.         These library modules aim to be small, efficient, compatible with standards, and hopefully
  17.         elegant.
  18.        
  19.         Copyright 2012, Mathscribe, Inc.  Dual licensed under the MIT or GPL Version 2 licenses.
  20.         See e.g. http://jquery.org/license for an explanation of these licenses.  */
  21.  
  22.  
  23. "use strict";
  24.  
  25.  
  26. var jsCurry = function() {
  27.         var sliceMF = Array.prototype.slice;    // slice "Member Function"
  28.        
  29.         // provide a few basic ECMAScript 5 functions if they are missing:
  30.         if (! Function.prototype.bind)
  31.                 Function.prototype.bind = function(thisArg /* , ... */) {
  32.                         var f = this, args0 = sliceMF.call(arguments, 1);
  33.                         return function(/* ... */) {
  34.                                 return f.apply(thisArg, args0.concat(sliceMF.call(arguments, 0)));
  35.                         };
  36.                 };
  37.         if (! String.prototype.trim)
  38.                 String.prototype.trim = function() { return String(this).replace(/^\s+|\s+$/g, ''); };
  39.         if (! Array.isArray)
  40.                 Array.isArray = function(x) {
  41.                         return typeof x == 'object' && x !== null &&
  42.                                 Object.prototype.toString.call(x) === '[object Array]';
  43.                 };
  44.         if (! Object.keys)
  45.                 Object.keys = function(obj) {
  46.                         var res = [];
  47.                         for (var p in obj)      if (obj.hasOwnProperty(p))      res.push(p);
  48.                         return res;
  49.                 };
  50.         if (! Date.now) Date.now = function() { return (new Date()).getTime(); };
  51.        
  52.         function F(x /* , ... */) {     // F() shorthand notation for some basic operations
  53.                 if (typeof x == 'function')     return F.curry.apply(undefined, arguments);
  54.                 if (arguments.length == 2) {
  55.                         var y = arguments[1];
  56.                         if (typeof x == 'string')       return y[x].bind(y);
  57.                         if (typeof y == 'function')
  58.                                 return (typeof x == 'number' ? F.aritize : F.partial)(x, y);
  59.                 }
  60.                 arguments.length == 1 || F.err(err_F_1_);
  61.                 if (typeof x == 'number' || typeof x == 'string')       return F.pToF(x);
  62.                 if (x.nodeType == 1 /* Element */)      // requires jQuery 1.4+; e.g. for widget ops
  63.                         return jQuery.data(x);
  64.                 if (x && typeof x == 'object')  return F.aToF(x);
  65.                 F.err(err_F_2_);
  66.         }
  67.        
  68.         F.err = function() { throw Error('Assertion failed'); };
  69.                 // usually argument evaluation intentionally fails, to report its line number
  70.        
  71.         F.id = function(x) { return x; };
  72.         F.constant = function(x) { return function(/* ... */) { return x; }; };
  73.                 // "const" is a reserved word in ECMAScript 3
  74.         F.applyF = function(f, args) { return f.apply(undefined, args); }
  75.         F.curry = function(f /* , ... */)
  76.                 { var g = f; arguments[0] = undefined; return g.bind.apply(g, arguments); };
  77.         F._ = {};       // needed since e.g. (0 in [ , 3]) is apparently wrong in e.g. Firefox 3.0
  78.         F.partial = function(a, f) {    // 'a' supplies some arguments to 'f'
  79.                 var n = a.length;
  80.                 return function(/* ... */) {
  81.                         var args = sliceMF.call(arguments, 0);
  82.                         for (var i = 0; i < n; i++)     if (a[i] !== F._)       args.splice(i, 0, a[i]);
  83.                         return f.apply(this, args);
  84.                 };
  85.         };
  86.         F.uncurry = function(f) { return function(x, y) { return f(x)(y); }; };
  87.         F.o = function(/* ... */) {     // composition of 1 or more functions
  88.                 var fs = arguments;
  89.                 return function(/* ... */) {
  90.                         var n = fs.length, res = fs[--n].apply(undefined, arguments);
  91.                         while (n > 0)   res = fs[--n](res);
  92.                         return res;
  93.                 };
  94.         };
  95.         F.oMap = function(f, g) // composition, using F.map(g, <arguments>)
  96.                 { return function(/* ... */) { return F.applyF(f, F.map(g, arguments)); }; };
  97.         F.flip = function(f) { return function(x, y) { return f(y, x); }; };
  98.         F.seqF = function(/* ... */) {
  99.                 var fs = arguments, n = fs.length;
  100.                 return function(/* ... */) {
  101.                         var y;
  102.                         for (var i = 0; i < n; i++)     y = fs[i].apply(undefined, arguments);
  103.                         return y;       // returns undefined if n == 0
  104.                 };
  105.         };
  106.         F.cor = function(/* ... */) {   // conditional or
  107.                 var fs = arguments;
  108.                 return function(/* ... */) { return F.any(F([F._, arguments], F.applyF), fs); };
  109.         };
  110.         F.aritize = function(n, f)      // for discarding optional trailing arguments
  111.                 { return function(/* ... */) { return F.applyF(f, sliceMF.call(arguments, 0, n)); }; };
  112.        
  113.         F.not = function(x) { return ! x; };
  114.        
  115.         /*  The following functions that act on arrays also work on "array-like" objects (with a
  116.                 'length' property), including array-like host objects.  The functions may or may not
  117.                 skip missing elements.  */
  118.        
  119.         // A "cmp" function returns 0, < 0, or > 0 for ==, <, or > respectively.
  120.         F.cmpX = function(x, y) { return x - y; };      // for finite numbers, or Dates
  121.         F.cmpJS = function(s, t) { return s < t ? -1 : s == t ? 0 : 1; };
  122.                 // JavaScript built-in comparison; for numbers, strings, or Dates; NaN => !=
  123.         F.cmpLex = function(cmpE, v, w) // "lexicographic order"; cmpE need not return a number
  124.                 { return F.any(function(e, i) { return i == w.length ? 1 : cmpE(e, w[i]); }, v) ||
  125.                         v.length - w.length; };
  126.         F.eqTo = function(x, cmpP) {
  127.                 if (! cmpP)     cmpP = function(y, z) { return y !== z; };
  128.                 return F.o(F.not, F(cmpP, x));
  129.         };
  130.        
  131.         F.pToF = function(p) { return function(obj) { return obj[p]; }; };
  132.         F.aToF = function(obj) { return function(p) { return obj[p]; }; };
  133.         F.fToA = function(f, n) {
  134.                 var a = new Array(n);
  135.                 for (var i = 0; i < n; i++)     a[i] = f(i);
  136.                 return a;
  137.         };
  138.         F.memoF = function(f, memo) {
  139.                 if (! memo)     memo = {};
  140.                 return function(p) { return memo.hasOwnProperty(p) ? memo[p] : (memo[p] = f(p)); };
  141.         };
  142.         F.replicate = function(n, e) { return F.fToA(F.constant(e), n); };
  143.         F.setF = function(obj, p, v) { obj[p] = v; };
  144.         F.obj1 = function(p, v) { var res = {}; res[p] = v; return res; };
  145.        
  146.         F.slice = function(a, startP, endP) {
  147.                 if (startP == null)     startP = 0;
  148.                 if (Array.isArray(a))
  149.                         return arguments.length < 3 ? a.slice(startP) : a.slice(startP, endP);
  150.                 var n = a.length;
  151.                 startP = startP < 0 ? Math.max(0, n + startP) : Math.min(n, startP);
  152.                 endP = endP == null ? n : endP < 0 ? Math.max(0, n + endP) : Math.min(n, endP);
  153.                 var res = [];
  154.                 while (startP < endP)   res.push(a[startP++]);
  155.                 return res;
  156.         };
  157.         F.array = function(/* ... */) { return sliceMF.call(arguments, 0); };
  158.         F.concatArgs = F.oMap(F('concat', []),
  159.                 function(a) { return Array.isArray(a) ? a : F.slice(a); });
  160.         F.concatMap = function(f, a) { return F.applyF(F.concatArgs, F.map(f, a)); };
  161.        
  162.         F.findIndex = function(qF, a) {
  163.                 var n = a.length;
  164.                 for (var i = 0; i < n; i++)     if (qF(a[i], i, a))     return i;
  165.                 return -1;
  166.         };
  167.         F.findLastIndex = function(qF, a) {
  168.                 for (var i = a.length; --i >= 0; )      if (qF(a[i], i, a))     return i;
  169.                 return -1;
  170.         };
  171.         F.find = function(qF, a) {
  172.                 var j = F.findIndex(qF, a);
  173.                 return j == -1 ? undefined : a[j];
  174.         };
  175.         F.elemIndex = function(e, a, cmpP) {
  176.                 if (a.indexOf && ! cmpP && Array.isArray(a))    return a.indexOf(e);
  177.                 return F.findIndex(F.eqTo(e, cmpP), a);
  178.         };
  179.         F.elemLastIndex = function(e, a, cmpP) {
  180.                 if (a.lastIndexOf && ! cmpP && Array.isArray(a))        return a.lastIndexOf(e);
  181.                 return F.findLastIndex(F.eqTo(e, cmpP), a);
  182.         };
  183.         F.elem = function(e, a, cmpP) { return F.elemIndex(e, a, cmpP) != -1; };
  184.         F.all = function(qF, a) {
  185.                 if (a.every && Array.isArray(a))        return a.every(qF);
  186.                 var n = a.length;
  187.                 for (var i = 0; i < n; i++)     if (! qF(a[i], i, a))   return false;
  188.                 return true;
  189.         };
  190.         F.any = function(f, a) /* note result may be non-boolean */ {
  191.                 var n = a.length, y = false /* in case n == 0 */;
  192.                 for (var i = 0; i < n; i++) {
  193.                         y = f(a[i], i, a);
  194.                         if (y)  return y;
  195.                 }
  196.                 return y;
  197.         };
  198.         F.iter = function(f, a /* , ... */) {
  199.                 if (arguments.length == 2) {    // for speed
  200.                         if (a.forEach && Array.isArray(a))      return a.forEach(f);
  201.                         var n = a.length;
  202.                         for (var i = 0; i < n; i++)     f(a[i], i, a);
  203.                 } else {
  204.                         arguments.length > 2 || F.err(err_iter_);
  205.                         var args = sliceMF.call(arguments, 1),
  206.                                 n = F.applyF(Math.min, F.map(F('length'), args));
  207.                         for (var i = 0; i < n; i++)     F.applyF(f, F.map(F(i), args).concat(i, args));
  208.                 }
  209.         };
  210.         F.map = function(f, a) {
  211.                 if (a.map && Array.isArray(a))  return a.map(f);
  212.                 var n = a.length, res = new Array(n);
  213.                 for (var i = 0; i < n; i++)     res[i] = f(a[i], i, a);
  214.                 return res;
  215.         };
  216.         F.map1 = function(f, a) { return F.map(F(1, f), a); };
  217.         F.zipWith = function(f /* , ... */) {
  218.                 arguments.length > 1 || F.err(err_zipWith_);
  219.                 var res = [];
  220.                 for (var i = 0; ; i++) {
  221.                         var args = [];
  222.                         for (var j = 1; j < arguments.length; j++) {
  223.                                 var a = arguments[j];
  224.                                 if (i < a.length)       args.push(a[i]);
  225.                                 else    return res;
  226.                         }
  227.                         res.push(F.applyF(f, args));
  228.                 }
  229.                 return res;
  230.         };
  231.         F.zip = F(F.zipWith, F.array);
  232.         F.unzip =       // matrix transpose
  233.                 function(zs) { return zs.length ? F.applyF(F.zip, zs) : []; };
  234.         F.filter = function(qF, a) {
  235.                 if (a.filter && Array.isArray(a))       return a.filter(qF);
  236.                 return F.fold(function(y, e, i, a) { if (qF(e, i, a)) y.push(e); return y; }, a, []);
  237.         };
  238.         F.fold = function(op, a, xOpt) {
  239.                 if (a.reduce && Array.isArray(a))
  240.                         return arguments.length < 3 ? a.reduce(op) : a.reduce(op, xOpt);
  241.                 var n = a.length, i = 0;
  242.                 if (arguments.length < 3)       xOpt = n ? a[i++] : F.err(err_fold_);
  243.                 for ( ; i < n; i++)     xOpt = op(xOpt, a[i], i, a);
  244.                 return xOpt;
  245.         };
  246.         F.foldR = function(op, a, xOpt) {       // similar to Haskell (foldr (flip op) xOpt a)
  247.                 if (a.reduceRight && Array.isArray(a))
  248.                         return arguments.length < 3 ? a.reduceRight(op) : a.reduceRight(op, xOpt);
  249.                 var n = a.length;
  250.                 if (arguments.length < 3)       xOpt = n ? a[--n] : F.err(err_foldR_);
  251.                 while (--n >= 0)        xOpt = op(xOpt, a[n], n, a);
  252.                 return xOpt;
  253.         };
  254.        
  255.         F.sum = function(a) {
  256.                 var n = a.length, res = 0;
  257.                 for (var i = 0; i < n; i++)     res += a[i];
  258.                 return res;
  259.         };
  260.        
  261.         F.test = function(t) {  // e.g. for dynamic type checking when appropriate
  262.                 if (t === 0 || t === '')        t = typeof t;
  263.                 if (typeof t == 'string')       return function(x) { return typeof x == t; };
  264.                 if (t === Array || t === Date || t === RegExp)  // assumes same frame
  265.                         return function(x) { return x != null && x.constructor == t; };
  266.                 if (t === null) return F.eqTo(null);
  267.                 if (t.constructor == RegExp)    return F('test', t);    // assumes same frame
  268.                 if (typeof t == 'function')     return t;
  269.                 if (Array.isArray(t)) {
  270.                         if (t.length == 1) {
  271.                                 t = F.test(t[0]);
  272.                                 return function(x) { return Array.isArray(x) && F.all(t, x); };
  273.                         } else {        // "or" of tests
  274.                                 t = F.map(F.test, t);
  275.                                 return function(x) { return F.any(function(qF) { return qF(x); }, t); };
  276.                         }
  277.                 }
  278.                 if (typeof t == 'object') {
  279.                         var ks = Object.keys(t), fs = F.map(F.o(F.test, F(t)), ks);
  280.                         return function(x)
  281.                                 { return x != null && F.all(function(k, i) { return fs[i](x[k]); }, ks); };
  282.                 }
  283.                 F.err(err_test_);
  284.         };
  285.        
  286.         return F;
  287. }();
  288. var F;  if (F === undefined)    F = jsCurry;
  289.