maddy.js | |
---|---|
/*!
* Maddy functional programming library
* http://github.com/kitgoncharov/maddy
*
* Copyright 2011, Kit Goncharov
* http://kitgoncharov.github.com
*
* Maddy is inspired by FuseJS and Underscore.
* Released under the MIT License.
*/
(function () { | |
Convenience aliases. | var toString = Object.prototype.toString, slice = Array.prototype.slice,
apply = Function.prototype.apply, |
Set up the | Maddy = typeof exports == 'object' && exports || (this.Maddy = { |
Restores the original value of the | 'noConflict': (function (exports) { |
Save the previous value of the global | var original = exports.Maddy;
function noConflict() {
exports.Maddy = original;
return Maddy;
}
return noConflict;
}(this))
}), |
Matches internal | className = /^\[object\s(.*?)\]$/, |
| forEach = Maddy.forEach = Maddy.each = (function () { |
The internal | function Properties() {
this.toString = 1;
}
Properties.prototype.toString = 1; |
Simulate | var hasOwnProperty = {}.hasOwnProperty || function (property) { |
Save the original prototype chain. | var original = this.__proto__, result; |
Break the prototype chain. | this.__proto__ = null;
result = property in this; |
Restore the prototype chain. | this.__proto__ = original;
return result;
}, length = 0, properties = new Properties, property, each; |
Iterate over a new instance of the | for (property in properties) { |
Ignore inherited properties to correctly detect iteration bugs. | if (hasOwnProperty.call(properties, property)) length++;
}
if (!length) { |
JScript ignores shadowed non-enumerable properties. | properties = ['constructor', 'toString', 'valueOf', 'isPrototypeOf', 'hasOwnProperty', 'propertyIsEnumerable', 'toLocaleString'];
each = function forEach(object, iterator, context) {
var index = 0, length = object.length, property;
if (typeof iterator != 'function') throw new TypeError;
if (length === +length) { |
Use a | for (index = 0; index < length; index++) { |
Break if the iterator function explicitly returns | if (index in object && iterator.call(context, object[index], index, object) === false) break;
}
} else {
for (property in object) { |
Break if the iterator function explicitly returns | if (hasOwnProperty.call(object, property) && iterator.call(context, object[property], property, object) === false) break;
} |
Provide a workaround for the JScript bug. | while ((property = properties[index++])) {
if (hasOwnProperty.call(object, property) && iterator.call(context, object[property], property, object) === false) break;
}
}
};
} else if (length == 2) { |
Safari 2 enumerates all shadowed and inherited properties. | each = function forEach(object, iterator, context) {
var index = 0, length = object.length, isFunction, properties, property;
if (typeof iterator != 'function') throw new TypeError;
if (length === +length) {
for (index = 0; index < length; index++) {
if (index in object && iterator.call(context, object[index], index, object) === false) break;
}
} else { |
Detect functions and skip iterating the | isFunction = getClassOf(object) == 'Function'; |
Create a cache of iterated properties. | properties = {};
for (property in object) { |
Cache each property to prevent enumeration of inherited and shadowed properties. | if (!(isFunction && property == 'prototype') && !hasOwnProperty.call(properties, property) && (properties[property] = 1) && hasOwnProperty.call(object, property) && iterator.call(context, object[property], property, object) === false) break;
}
}
};
} else {
each = function forEach(object, iterator, context) {
var index = 0, length = object.length, isFunction, property;
if (typeof iterator != 'function') throw new TypeError;
if (length === +length) {
for (index = 0; index < length; index++) {
if (index in object && iterator.call(context, object[index], index, object) === false) break;
}
} else {
isFunction = getClassOf(object) == 'Function';
for (property in object) {
if (!(isFunction && property == 'prototype') && hasOwnProperty.call(object, property) && iterator.call(context, object[property], property, object) === false) break;
}
}
};
}
return each;
}()); |
The current version of Maddy. | Maddy.version = '0.1.0'; |
| Maddy.map = map;
function map(object, iterator, context) {
var results = [];
if (typeof iterator != 'function') throw new TypeError;
forEach(object, function (value, property, object) {
results.push(iterator.call(context, value, property, object));
});
return results;
} |
| Maddy.reduce = Maddy.foldl = Maddy.inject = reduce;
function reduce(object, iterator, memo, context) {
var index = 0, length = +object.length;
if (typeof iterator != 'function' || !length && arguments.length == 1) throw new TypeError;
if (arguments.length < 3) {
do {
if (index in object) {
memo = object[index++];
break;
}
if (++index >= length) throw new TypeError;
} while (true);
}
for (; index < length; index++) {
if (index in object && (memo = iterator.call(context, memo, object[index], index, object)) === false) break;
}
return memo;
} |
| Maddy.reduceRight = Maddy.foldr = reduceRight;
function reduceRight(object, iterator, memo, context) {
var length = +object.length, index = length - 1;
if (typeof iterator != 'function' || !length && arguments.length == 1) throw new TypeError;
if (arguments.length < 3) {
do {
if (index in object) {
memo = object[index--];
break;
}
if (--index < 0) throw new TypeError;
} while (true);
}
for (; index >= 0; index--) {
if (index in object && (memo = iterator.call(context, memo, object[index], index, object)) === false) break;
}
return memo;
} |
| Maddy.some = Maddy.any = some;
function some(object, iterator, context) {
var result = false;
if (typeof iterator != 'function') throw new TypeError;
forEach(object, function (value, property, object) {
if ((result = iterator.call(context, value, property, object))) return false;
});
return result;
} |
| Maddy.filter = Maddy.select = filter;
function filter(object, iterator, context) {
var results = [];
if (typeof iterator != 'function') throw new TypeError;
forEach(object, function (value, property, object) {
if (iterator.call(context, value, property, object)) results.push(value);
});
return results;
} |
| Maddy.every = Maddy.all = every;
function every(object, iterator, context) {
var result = true;
if (typeof iterator != 'function') throw new TypeError;
forEach(object, function (value, property, object) {
if (!(result = iterator.call(context, value, property, object))) return false;
});
return result;
} |
| Maddy.find = Maddy.detect = find;
function find(object, iterator, context) {
var result;
if (typeof iterator != 'function') throw new TypeError;
some(object, function (value, property, object) {
if (iterator.call(context, value, property, object)) {
result = value;
return false;
}
});
return result;
} |
| Maddy.reject = reject;
function reject(object, iterator, context) {
var results = [];
if (typeof iterator != 'function') throw new TypeError;
forEach(object, function (value, property, object) {
if (!iterator.call(context, value, property, object)) results.push(value);
});
return results;
} |
| Maddy.include = Maddy.contains = include;
function include(object, element) {
var contains = false;
some(object, function (value) {
return (contains = value === element);
});
return contains;
} |
| Maddy.invoke = invoke;
function invoke(object, method) {
var parameters = slice.call(arguments, 2);
return map(object, function (value) {
return apply.call(value[method], value, parameters);
});
} |
| Maddy.pluck = pluck;
function pluck(object, property) {
return map(object, function (value) {
return value[property];
});
} |
| Maddy.max = max;
function max(object, iterator, context) {
var comparable, computed, result = null;
if (!iterator && (iterator = Maddy.identity) && getClassOf(object) == 'Array') {
result = Math.max.apply(Math, object); |
Ensure that the result is not | if (result == result) return result;
result = null;
}
forEach(object, function (value, property, object) {
comparable = iterator.call(context, value, property, object);
if (computed == null || comparable > computed) {
computed = comparable;
result = value;
}
});
return result;
} |
| Maddy.min = min;
function min(object, iterator, context) {
var comparable, computed, result = null;
if (!iterator && (iterator = Maddy.identity) && getClassOf(object) == 'Array') {
result = Math.min.apply(Math, object);
if (result == result) return result;
result = null;
}
forEach(object, function (value, property, object) {
comparable = iterator.call(context, value, property, object);
if (computed == null || comparable < computed) {
computed = comparable;
result = value;
}
});
return result;
}
|
| Maddy.identity = identity;
function identity(value) {
return value;
} |
| Maddy.partition = partition;
function partition(object, iterator, context) {
var trues = [], falses = [];
forEach(object, function (value, property, object) {
(iterator.call(context, value, property, object) ? trues : falses).push(value);
});
return [trues, falses];
} |
| Maddy.sortBy = sortBy;
function sortBy(object, iterator, context) {
return pluck(map(object, function (value, property, object) {
return {
'value': value,
'criteria': iterator.call(context, value, property, object)
};
}).sort(function (left, right) {
left = left.criteria;
right = right.criteria;
return left < right ? -1 : left > right ? 1 : 0;
}), 'value');
} |
| Maddy.groupBy = groupBy;
function groupBy(object, iterator, context) {
var results = {};
forEach(object, function (value, property, object) {
var key = iterator.call(context, value, property, object);
(results[key] || (results[key] = [])).push(value);
});
return results;
} |
| Maddy.first = first;
function first(object, iterator, context) {
var index = -1, length = +object.length;
if (iterator == null) {
while (++index < length) {
if (index in object) return object[index];
}
} else if (typeof iterator == 'function') {
while (++index < length) { |
Return the first element for which the function returns true. | if (iterator.call(context, object[index], index, object)) return object[index];
}
} else { |
Coerce the provided index to a number. | index = +iterator;
if (index != index) return []; |
Return the specified number of elements. | index = index < 0 ? 0 : index > length ? length : index;
return slice.call(object, 0, index);
}
} |
| Maddy.last = last;
function last(object, iterator, context) {
var length = +object.length, index;
if (iterator == null) {
return object[length && length - 1];
} else if (typeof iterator == 'function') {
while (length--) {
if (iterator.call(context, object[length], length, object)) return object[length];
}
} else {
index = +iterator;
if (index != index) return [];
index = index < 0 ? 0 : index > length ? length : index;
return slice.call(object, length - index);
}
} |
| Maddy.compact = compact;
function compact(object) {
return filter(object, function (value) {
return value != null;
});
} |
| Maddy.flatten = flatten;
function flatten(object) {
var results = [];
forEach(object, function (value) {
var length, source, sourceLength;
if (getClassOf(value) == 'Array') {
length = results.length;
source = flatten(value);
sourceLength = source.length;
while (sourceLength--) results[length + sourceLength] = source[sourceLength];
} else {
results.push(value);
}
});
return results;
} |
| Maddy.without = without;
function without(object) {
var parameters = slice.call(arguments, 1);
return reject(object, function (value) {
return include(parameters, value);
});
} |
| Maddy.unique = Maddy.uniq = unique;
function unique(object) {
var results = [];
forEach(object, function (value) {
if (!include(results, value)) results.push(value);
});
return results;
} |
| Maddy.intersect = intersect;
function intersect(first, second) {
var results = [];
forEach(first, function (value) {
if (include(second, value) && !include(results, value)) results.push(value);
});
return results;
} |
| Maddy.zip = zip;
function zip(object) {
var parameters = arguments;
return map(object, function (value, property) {
return pluck(parameters, property);
});
} |
| Maddy.indexOf = indexOf;
function indexOf(object, element, index) {
var length = +object.length;
if (!length) return -1;
index = +index || 0;
if (index < 0) index = length + index;
index--;
while (++index < length) {
if (index in object && object[index] === element) return index;
}
return -1;
} |
| Maddy.lastIndexOf = lastIndexOf;
function lastIndexOf(object, element, index) {
var length = +object.length;
index = index == null ? length : +index;
if (!length) {
return -1;
} else if (index > length) {
index = length - 1;
} else if (index < 0) {
index = length + index;
}
index++;
while (--index > -1) {
if (index in object && object[index] === element) break;
}
return index;
} |
| Maddy.range = range;
function range(first, last, difference) {
var index = 0, results = [], length;
if (arguments.length <= 1) {
last = first > 0 ? +first : 0;
first = 0; |
Invalid argument; return an empty range. | if (!last) return results;
} |
Check if the provided difference is valid. | if (!(difference = +difference)) difference = 1;
results = Array((length = Math.max(Math.ceil((last - first) / difference), 0)));
while (index < length) {
results[index++] = first;
first += difference;
}
return results;
} |
| Maddy.keys = keys;
function keys(object) {
var results = [];
if (object !== Object(object)) throw new TypeError;
forEach(object, function (value, key) {
results.push(String(key));
});
return results;
} |
| Maddy.values = values;
function values(object) {
var results = [];
if (object != Object(object)) throw new TypeError;
forEach(object, function (value) {
results.push(value);
});
return results;
} |
| Maddy.extend = extend;
function extend(destination) { |
Copies each property to the destination object. | function iterator(value, property) {
destination[property] = value;
}
for (var index = 1, length = arguments.length; index < length; index++) {
forEach(arguments[index], iterator);
}
return destination;
} |
| Maddy.isEqual = (function () { |
Based on work by Jeremy Ashkenas, Philippe Rathe, and Mark Miller. | function eq(left, right, stack) {
var className, result, size, sizeRight; |
Identical objects are equal. Note: | if (left === right) return left != 0 || 1 / left == 1 / right; |
Compare | className = getClassOf(left);
if (className != getClassOf(right)) return false;
switch (className) { |
| case 'Null':
case 'Undefined':
case 'Function':
return left == right; |
Strings, numbers, dates, and booleans are compared by value. | case 'String': |
Primitives and their corresponding objects are equivalent. | return '' + left == '' + right;
case 'Number':
case 'Date':
case 'Boolean':
left = +left;
right = +right; |
| return left != left ? right != right : left == right; |
RegExps are compared by their source patterns and flags. | case 'RegExp':
return left.source == right.source && left.global == right.global && left.multiline == right.multiline && left.ignoreCase == right.ignoreCase;
} |
Recursively compare objects and arrays. | if (typeof left != 'object') return false; |
Assume equality for cyclic structures. | size = stack.length;
while (size--) {
if (stack[size] == left) return true;
} |
Add the object to the stack of traversed objects. | stack.push(left);
result = true;
size = sizeRight = 0;
if (className == 'Array') { |
Compare array lengths to determine if a deep comparison is necessary. | size = left.length;
result = size == right.length;
if (result) {
while (size--) {
if (size in left && !(result = size in right && eq(left[size], right[size], stack))) break;
}
}
} else {
forEach(left, function (value, property) { |
Count the expected number of properties. | size++; |
Deep compare each object member. | if (!(result = property in right && eq(value, right[property], stack))) return false;
}); |
Ensure that both objects contain the same number of properties. | if (result) {
forEach(right, function () { |
Break as soon as the expected property count is greater. | if (++sizeRight > size) return false;
});
result = size == sizeRight;
}
} |
Remove the object from the traversed objects stack. | stack.pop();
return result;
} |
Expose the recursive | function isEqual(left, right) {
return eq(left, right, []);
}
return isEqual;
}()); |
| Maddy.isEmpty = isEmpty;
function isEmpty(object) {
var className = getClassOf(object), result = true;
switch (className) { |
| case 'Null':
case 'Undefined':
return true; |
Most native ECMAScript objects are not empty. | case 'Date':
case 'Boolean':
case 'Function':
case 'RegExp':
return false; |
Empty strings and arrays contain a | case 'String':
case 'Array':
return !object.length;
default:
forEach(object, function () { |
This callback should never be invoked for an empty object. | return (result = false);
});
}
return result;
} |
| Maddy.getClassOf = getClassOf;
function getClassOf(object) {
var match; |
| if (object == null) {
return 'Null';
} else if (typeof object == 'undefined') {
return 'Undefined';
} else { |
In some environments (e.g, Mozilla Rhino), | match = className.exec(Object.prototype.toString.call(object));
return match && match[1] || 'Object';
}
} |
| Maddy.times = times;
function times(count, iterator, context) {
var index = -1;
while (++index < count && iterator.call(context, index) !== false);
}
}).call(this);
|