functional.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. if (!dojo._hasResource["dojox.lang.functional"]) { // _hasResource checks added
  2. // by build. Do not use
  3. // _hasResource directly in
  4. // your code.
  5. dojo._hasResource["dojox.lang.functional"] = true;
  6. dojo.provide("dojox.lang.functional");
  7. // This module adds high-level functions and related constructs:
  8. // - list comprehensions similar to JavaScript 1.7
  9. // - anonymous functions built from the string
  10. // - zip combiners
  11. // - "reduce" family of functions
  12. // - currying and partial functions
  13. // - argument pre-processing: mixer and flip
  14. // - miscellaneous useful functions
  15. // Acknoledgements:
  16. // - parts of this module (most notably lambda, constFun, invoke, pluck, and
  17. // partial)
  18. // are based on work by Oliver Steele
  19. // (http://osteele.com/sources/javascript/functional/functional.js)
  20. // which was published under MIT License
  21. // - Simple "maybe" monad was donated by Alex Russell.
  22. // Notes:
  23. // - Dojo provides following high-level functions in dojo/_base/array.js:
  24. // forEach, map, filter, every, some
  25. // - These functions implemented with optional lambda expression as a
  26. // parameter.
  27. // - missing high-level functions are provided with the compatible API:
  28. // foldl, foldl1, scanl, scanl1, foldr, foldr1, scanr, scanr1,
  29. // reduce, reduceRight
  30. // - lambda() and listcomp() produce functions, which after the compilation
  31. // step are
  32. // as fast as regular JS functions (at least theoretically).
  33. (function() {
  34. var d = dojo, df = dojox.lang.functional, g_re = /\bfor\b|\bif\b/gm, empty = {};
  35. // split() is augmented on IE6 to ensure the uniform behavior
  36. var split = "ab".split(/a*/).length > 1
  37. ? String.prototype.split
  38. : function(sep) {
  39. var r = this.split.call(this, sep), m = sep.exec(this);
  40. if (m && m.index == 0) {
  41. r.unshift("");
  42. }
  43. return r;
  44. };
  45. var lambda = function(/* String */s) {
  46. var args = [], sects = split.call(s, /\s*->\s*/m);
  47. if (sects.length > 1) {
  48. while (sects.length) {
  49. s = sects.pop();
  50. args = sects.pop().split(/\s*,\s*|\s+/m);
  51. if (sects.length) {
  52. sects.push("(function(" + args + "){return (" + s
  53. + ")})");
  54. }
  55. }
  56. } else if (s.match(/\b_\b/)) {
  57. args = ["_"];
  58. } else {
  59. var l = s.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m), r = s
  60. .match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
  61. if (l || r) {
  62. if (l) {
  63. args.push("$1");
  64. s = "$1" + s;
  65. }
  66. if (r) {
  67. args.push("$2");
  68. s = s + "$2";
  69. }
  70. } else {
  71. var vars = s
  72. .replace(
  73. /(?:\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,
  74. "").match(/([a-z_$][a-z_$\d]*)/gi)
  75. || [];
  76. var t = {};
  77. d.forEach(vars, function(v) {
  78. if (!(v in t)) {
  79. args.push(v);
  80. t[v] = 1;
  81. }
  82. });
  83. }
  84. }
  85. return {
  86. args : args,
  87. body : "return (" + s + ");"
  88. }; // Object
  89. };
  90. var listcomp = function(/* String */s) {
  91. var frag = s.split(g_re), act = s.match(g_re), head = ["var r = [];"], tail = [];
  92. for (var i = 0; i < act.length;) {
  93. var a = act[i], f = frag[++i];
  94. if (a == "for" && !/^\s*\(\s*(;|var)/.test(f)) {
  95. f = f.replace(/^\s*\(/, "(var ");
  96. }
  97. head.push(a, f, "{");
  98. tail.push("}");
  99. }
  100. return head.join("") + "r.push(" + frag[0] + ");" + tail.join("")
  101. + "return r;"; // String
  102. };
  103. var currying = function(/* Object */info) {
  104. return function() { // Function
  105. if (arguments.length + info.args.length < info.arity) {
  106. return currying({
  107. func : info.func,
  108. arity : info.arity,
  109. args : Array.prototype.concat.apply(info.args,
  110. arguments)
  111. });
  112. }
  113. return info.func.apply(this, Array.prototype.concat.apply(
  114. info.args, arguments));
  115. };
  116. };
  117. var identity = function(x) {
  118. return x;
  119. };
  120. var compose = function(/* Array */a) {
  121. return a.length ? function() {
  122. var i = a.length - 1, x = df.lambda(a[i])
  123. .apply(this, arguments);
  124. for (--i; i >= 0; --i) {
  125. x = df.lambda(a[i]).call(this, x);
  126. }
  127. return x;
  128. } : identity;
  129. };
  130. d.mixin(df, {
  131. // lambda
  132. buildLambda : function(/* String */s) {
  133. // summary: builds a function from a snippet, returns a string,
  134. // which represents the function.
  135. // description: This method returns a textual representation of
  136. // a function
  137. // built from the snippet. It is meant to be evaled in the
  138. // proper context,
  139. // so local variables can be pulled from the environment.
  140. s = lambda(s);
  141. return "function(" + s.args.join(",") + "){" + s.body + "}"; // String
  142. },
  143. lambda : function(/* Function|String|Array */s) {
  144. // summary: builds a function from a snippet, or array
  145. // (composing), returns
  146. // a function object; functions are passed through unmodified.
  147. // description: This method is used to normalize a functional
  148. // representation
  149. // (a text snippet, an array, or a function) to a function
  150. // object.
  151. if (typeof s == "function") {
  152. return s;
  153. }
  154. if (s instanceof Array) {
  155. return compose(s);
  156. }
  157. s = lambda(s);
  158. return new Function(s.args, s.body); // Function
  159. },
  160. // sequence generators
  161. repeat : function(/* Number */n, /* Function|String|Array */f, /* Object */
  162. z, /* Object? */o) {
  163. // summary: builds an array by repeatedly applying a unary
  164. // function N times
  165. // with a seed value Z.
  166. o = o || d.global;
  167. f = df.lambda(f);
  168. var t = new Array(n);
  169. t[0] = z;
  170. for (var i = 1; i < n; t[i] = z = f.call(o, z), ++i);
  171. return t; // Array
  172. },
  173. until : function(/* Function|String|Array */pr, /* Function|String|Array */
  174. f, /* Object */z, /* Object? */o) {
  175. // summary: builds an array by repeatedly applying a unary
  176. // function with
  177. // a seed value Z until the predicate is satisfied.
  178. o = o || d.global;
  179. f = df.lambda(f);
  180. pr = df.lambda(pr);
  181. var t = [];
  182. for (; !pr.call(o, z); t.push(z), z = f.call(o, z));
  183. return t; // Array
  184. },
  185. buildListcomp : function(/* String */s) {
  186. // summary: builds a function from a text snippet, which
  187. // represents a valid
  188. // JS 1.7 list comprehension, returns a string, which represents
  189. // the function.
  190. // description: This method returns a textual representation of
  191. // a function
  192. // built from the list comprehension text snippet (conformant to
  193. // JS 1.7).
  194. // It is meant to be evaled in the proper context, so local
  195. // variable can be
  196. // pulled from the environment.
  197. return "function(){" + listcomp(s) + "}"; // String
  198. },
  199. compileListcomp : function(/* String */s) {
  200. // summary: builds a function from a text snippet, which
  201. // represents a valid
  202. // JS 1.7 list comprehension, returns a function object.
  203. // description: This method returns a function built from the
  204. // list
  205. // comprehension text snippet (conformant to JS 1.7). It is
  206. // meant to be
  207. // reused several times.
  208. return new Function([], listcomp(s)); // Function
  209. },
  210. listcomp : function(/* String */s) {
  211. // summary: executes the list comprehension building an array.
  212. return (new Function([], listcomp(s)))(); // Array
  213. },
  214. // classic reduce-class functions
  215. foldl : function(/* Array */a, /* Function */f, /* Object */z, /* Object? */
  216. o) {
  217. // summary: repeatedly applies a binary function to an array
  218. // from left
  219. // to right using a seed value as a starting point; returns the
  220. // final
  221. // value.
  222. a = typeof a == "string" ? a.split("") : a;
  223. o = o || d.global;
  224. f = df.lambda(f);
  225. for (var i = 0; i < a.length; z = f.call(o, z, a[i], i, a), ++i);
  226. return z; // Object
  227. },
  228. foldl1 : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  229. o) {
  230. // summary: repeatedly applies a binary function to an array
  231. // from left
  232. // to right; returns the final value.
  233. a = typeof a == "string" ? a.split("") : a;
  234. o = o || d.global;
  235. f = df.lambda(f);
  236. var z = a[0];
  237. for (var i = 1; i < a.length; z = f.call(o, z, a[i], i, a), ++i);
  238. return z; // Object
  239. },
  240. scanl : function(/* Array */a, /* Function|String|Array */f, /* Object */
  241. z, /* Object? */o) {
  242. // summary: repeatedly applies a binary function to an array
  243. // from left
  244. // to right using a seed value as a starting point; returns an
  245. // array
  246. // of values produced by foldl() at that point.
  247. a = typeof a == "string" ? a.split("") : a;
  248. o = o || d.global;
  249. f = df.lambda(f);
  250. var n = a.length, t = new Array(n + 1);
  251. t[0] = z;
  252. for (var i = 0; i < n; z = f.call(o, z, a[i], i, a), t[++i] = z);
  253. return t; // Array
  254. },
  255. scanl1 : function(/* Array */a, /* Function|String|Array */f, /* Object */
  256. z, /* Object? */o) {
  257. // summary: repeatedly applies a binary function to an array
  258. // from left
  259. // to right; returns an array of values produced by foldl1() at
  260. // that
  261. // point.
  262. a = typeof a == "string" ? a.split("") : a;
  263. o = o || d.global;
  264. f = df.lambda(f);
  265. var n = a.length, t = new Array(n), z = a[0];
  266. t[0] = z;
  267. for (var i = 1; i < n; z = f.call(o, z, a[i], i, a), t[i++] = z);
  268. return t; // Array
  269. },
  270. foldr : function(/* Array */a, /* Function|String|Array */f, /* Object */
  271. z, /* Object? */o) {
  272. // summary: repeatedly applies a binary function to an array
  273. // from right
  274. // to left using a seed value as a starting point; returns the
  275. // final
  276. // value.
  277. a = typeof a == "string" ? a.split("") : a;
  278. o = o || d.global;
  279. f = df.lambda(f);
  280. for (var i = a.length; i > 0; --i, z = f.call(o, z, a[i], i, a));
  281. return z; // Object
  282. },
  283. foldr1 : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  284. o) {
  285. // summary: repeatedly applies a binary function to an array
  286. // from right
  287. // to left; returns the final value.
  288. a = typeof a == "string" ? a.split("") : a;
  289. o = o || d.global;
  290. f = df.lambda(f);
  291. var n = a.length, z = a[n - 1];
  292. for (var i = n - 1; i > 0; --i, z = f.call(o, z, a[i], i, a));
  293. return z; // Object
  294. },
  295. scanr : function(/* Array */a, /* Function|String|Array */f, /* Object */
  296. z, /* Object? */o) {
  297. // summary: repeatedly applies a binary function to an array
  298. // from right
  299. // to left using a seed value as a starting point; returns an
  300. // array
  301. // of values produced by foldr() at that point.
  302. a = typeof a == "string" ? a.split("") : a;
  303. o = o || d.global;
  304. f = df.lambda(f);
  305. var n = a.length, t = new Array(n + 1);
  306. t[n] = z;
  307. for (var i = n; i > 0; --i, z = f.call(o, z, a[i], i, a), t[i] = z);
  308. return t; // Array
  309. },
  310. scanr1 : function(/* Array */a, /* Function|String|Array */f, /* Object */
  311. z, /* Object? */o) {
  312. // summary: repeatedly applies a binary function to an array
  313. // from right
  314. // to left; returns an array of values produced by foldr1() at
  315. // that
  316. // point.
  317. a = typeof a == "string" ? a.split("") : a;
  318. o = o || d.global;
  319. f = df.lambda(f);
  320. var n = a.length, t = new Array(n), z = a[n - 1];
  321. t[n - 1] = z;
  322. for (var i = n - 1; i > 0; --i, z = f.call(o, z, a[i], i, a), t[i] = z);
  323. return t; // Array
  324. },
  325. // JS 1.6 standard array functions, which can take a lambda as a
  326. // parameter.
  327. // Consider using dojo._base.array functions, if you don't need the
  328. // lambda support.
  329. filter : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  330. o) {
  331. // summary: creates a new array with all elements that pass the
  332. // test
  333. // implemented by the provided function.
  334. a = typeof a == "string" ? a.split("") : a;
  335. o = o || d.global;
  336. f = df.lambda(f);
  337. var n = a.length, t = [], v;
  338. for (var i = 0; i < n; ++i) {
  339. v = a[i];
  340. if (f.call(o, v, i, a)) {
  341. t.push(v);
  342. }
  343. }
  344. return t; // Array
  345. },
  346. forEach : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  347. o) {
  348. // summary: executes a provided function once per array element.
  349. a = typeof a == "string" ? a.split("") : a;
  350. o = o || d.global;
  351. f = df.lambda(f);
  352. var n = a.length;
  353. for (var i = 0; i < n; f.call(o, a[i], i, a), ++i);
  354. },
  355. map : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  356. o) {
  357. // summary: creates a new array with the results of calling
  358. // a provided function on every element in this array.
  359. a = typeof a == "string" ? a.split("") : a;
  360. o = o || d.global;
  361. f = df.lambda(f);
  362. var n = a.length, t = new Array(n);
  363. for (var i = 0; i < n; t[i] = f.call(o, a[i], i, a), ++i);
  364. return t; // Array
  365. },
  366. every : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  367. o) {
  368. // summary: tests whether all elements in the array pass the
  369. // test
  370. // implemented by the provided function.
  371. a = typeof a == "string" ? a.split("") : a;
  372. o = o || d.global;
  373. f = df.lambda(f);
  374. var n = a.length;
  375. for (var i = 0; i < n; ++i) {
  376. if (!f.call(o, a[i], i, a)) {
  377. return false; // Boolean
  378. }
  379. }
  380. return true; // Boolean
  381. },
  382. some : function(/* Array */a, /* Function|String|Array */f, /* Object? */
  383. o) {
  384. // summary: tests whether some element in the array passes the
  385. // test
  386. // implemented by the provided function.
  387. a = typeof a == "string" ? a.split("") : a;
  388. o = o || d.global;
  389. f = df.lambda(f);
  390. var n = a.length;
  391. for (var i = 0; i < n; ++i) {
  392. if (f.call(o, a[i], i, a)) {
  393. return true; // Boolean
  394. }
  395. }
  396. return false; // Boolean
  397. },
  398. // JS 1.8 standard array functions, which can take a lambda as a
  399. // parameter.
  400. reduce : function(/* Array */a, /* Function */f, /* Object? */z) {
  401. // summary: apply a function simultaneously against two values
  402. // of the array
  403. // (from left-to-right) as to reduce it to a single value.
  404. return arguments.length < 3 ? df.foldl1(a, f) : df.foldl(a, f,
  405. z); // Object
  406. },
  407. reduceRight : function(/* Array */a, /* Function */f, /* Object? */z) {
  408. // summary: apply a function simultaneously against two values
  409. // of the array
  410. // (from right-to-left) as to reduce it to a single value.
  411. return arguments.length < 3 ? df.foldr1(a, f) : df.foldr(a, f,
  412. z); // Object
  413. },
  414. // currying and partial functions
  415. curry : function(/* Function|String|Array */f, /* Number? */arity) {
  416. // summary: curries a function until the arity is satisfied, at
  417. // which point it returns the calculated value.
  418. f = df.lambda(f);
  419. arity = typeof arity == "number" ? arity : f.length;
  420. return currying({
  421. func : f,
  422. arity : arity,
  423. args : []
  424. }); // Function
  425. },
  426. arg : {}, // marker for missing arguments
  427. partial : function(/* Function|String|Array */f) {
  428. // summary: creates a function where some arguments are bound,
  429. // and
  430. // some arguments (marked as dojox.lang.functional.arg) are will
  431. // be
  432. // accepted by the final function in the order they are
  433. // encountered.
  434. // description: This method is used to produce partially bound
  435. // functions. If you want to change the order of arguments, use
  436. // dojox.lang.functional.mixer() or
  437. // dojox.lang.functional.flip().
  438. var a = arguments, args = new Array(a.length - 1), p = [];
  439. f = df.lambda(f);
  440. for (var i = 1; i < a.length; ++i) {
  441. var t = a[i];
  442. args[i - 1] = t;
  443. if (t == df.arg) {
  444. p.push(i - 1);
  445. }
  446. }
  447. return function() { // Function
  448. var t = Array.prototype.slice.call(args, 0); // clone the
  449. // array
  450. for (var i = 0; i < p.length; ++i) {
  451. t[p[i]] = arguments[i];
  452. }
  453. return f.apply(this, t);
  454. };
  455. },
  456. // argument pre-processing
  457. mixer : function(/* Function|String|Array */f, /* Array */mix) {
  458. // summary: changes the order of arguments using an array of
  459. // numbers mix --- i-th argument comes from mix[i]-th place
  460. // of supplied arguments.
  461. f = df.lambda(f);
  462. return function() { // Function
  463. var t = new Array(mix.length);
  464. for (var i = 0; i < mix.length; ++i) {
  465. t[i] = arguments[mix[i]];
  466. }
  467. return f.apply(this, t);
  468. };
  469. },
  470. flip : function(/* Function|String|Array */f) {
  471. // summary: changes the order of arguments by reversing their
  472. // order.
  473. f = df.lambda(f);
  474. return function() { // Function
  475. // reverse arguments
  476. var a = arguments, l = a.length - 1, t = new Array(l + 1), i;
  477. for (i = 0; i <= l; ++i) {
  478. t[l - i] = a[i];
  479. }
  480. return f.apply(this, t);
  481. };
  482. },
  483. // combiners
  484. zip : function() {
  485. // summary: returns an array of arrays, where the i-th array
  486. // contains the i-th element from each of the argument arrays.
  487. // description: This is the venerable zip combiner (for example,
  488. // see Python documentation for general details). The returned
  489. // array is truncated to match the length of the shortest input
  490. // array.
  491. var n = arguments[0].length, m = arguments.length, i;
  492. for (i = 1; i < m; n = Math.min(n, arguments[i++].length));
  493. var t = new Array(n), j;
  494. for (i = 0; i < n; ++i) {
  495. var p = new Array(m);
  496. for (j = 0; j < m; p[j] = arguments[j][i], ++j);
  497. t[i] = p;
  498. }
  499. return t; // Array
  500. },
  501. unzip : function(/* Array */a) {
  502. // summary: similar to dojox.lang.functional.zip(), but takes
  503. // a single array of arrays as the input.
  504. // description: This function is similar to
  505. // dojox.lang.functional.zip()
  506. // and can be used to unzip objects packed by
  507. // dojox.lang.functional.zip(). It is here mostly to provide
  508. // a short-cut for the different method signature.
  509. return df.zip.apply(null, a); // Array
  510. },
  511. // miscelaneous functional adapters
  512. constFun : function(/* Object */x) {
  513. // summary: returns a function, which produces a constant value
  514. // regardless of supplied parameters.
  515. return function() {
  516. return x;
  517. }; // Function
  518. },
  519. invoke : function(/* String */m) {
  520. // summary: returns a function, which invokes a method on
  521. // supplied
  522. // object using optional parameters.
  523. return function(/* Object */o) { // Function
  524. return o[m].apply(o, Array.prototype.slice.call(arguments,
  525. 1));
  526. };
  527. },
  528. pluck : function(/* String */m) {
  529. // summary: returns a function, which returns a named object
  530. // member.
  531. return function(/* Object */o) { // Function
  532. return o[m];
  533. };
  534. },
  535. // object helpers
  536. forIn : function(/* Object */obj, /* Function|String|Array */f, /* Object? */
  537. o) {
  538. // summary: iterates over all object members skipping members,
  539. // which
  540. // are present in the empty object (IE and/or 3rd-party
  541. // libraries).
  542. o = o || d.global;
  543. f = df.lambda(f);
  544. for (var i in obj) {
  545. if (i in empty) {
  546. continue;
  547. }
  548. f.call(o, obj[i], i, obj);
  549. }
  550. },
  551. forEachReversed : function(/* Array */a, /* Function|String|Array */
  552. f, /* Object? */o) {
  553. // summary: executes a provided function once per array element.
  554. a = typeof a == "string" ? a.split("") : a;
  555. o = o || d.global;
  556. f = df.lambda(f);
  557. for (var i = a.length - 1; i >= 0; f.call(o, a[i], i, a), --i);
  558. }
  559. });
  560. // monads
  561. dojo.declare("dojox.lang.functional.MaybeMonad", null, {
  562. constructor : function(/* Object */value) {
  563. // summary: constructs a monad optionally initializing
  564. // all additional members
  565. if (arguments.length) {
  566. this.value = value;
  567. }
  568. },
  569. bind : function(/* dojox.lang.functional.Monad */monad, /* Function|String|Array */
  570. f, /* Object? */o) {
  571. // summary: this is the classic bind method, which
  572. // applies a function to a monad,
  573. // and returns a result as a monad; it is meant to be
  574. // overwritten to incorporate
  575. // side effects
  576. if (!("value" in monad)) {
  577. return new this.constructor(); // dojox.lang.functional.MaybeMonad
  578. }
  579. // => possible side-effects go here
  580. o = o || d.global;
  581. f = df.lambda(f);
  582. return f.call(o, monad.value); // dojox.lang.functional.Monad
  583. },
  584. // class-specific methods
  585. isNothing : function() {
  586. // summary: check if there is no bound value.
  587. return !("value" in this); // Boolean
  588. }
  589. });
  590. df.MaybeMonad.returnMonad = function(/* Object */value) {
  591. // summary: puts a valye in the Maybe monad.
  592. return new df.MaybeMonad(value); // dojox.lang.functional.MaybeMonad
  593. };
  594. df.MaybeMonad.zero = new df.MaybeMonad();
  595. })();
  596. }