Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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