Maddy

Maddy is a functional object operations library. It provides various higher-order functions typical of a functional programming library, as well as specialized methods for determining deep equality and recursively serializing objects. All recursive methods fully support cyclic structures.

Maddy is compatible with web browsers, CommonJS environments, JavaScript engines, and asynchronous script loaders. It also normalizes infamous bugs present in older environments.

The project is hosted on GitHub, along with the unit tests and annotated source code. You can use the GitHub issue tracker to submit bug reports and discuss feature requests, or send tweets to @kitcambridge.

Downloads

The current version is 0.3.0.

Development Version Download (26 KB, commented and uncompressed)
Production Version Download (2.3 KB, compressed using Closure Compiler)

Table of Contents

Utility Methods noConflict, curry
Generic Methods isPropertyOf, getClassOf, isEqual, stringify, isEmpty, keys, extend
Functional Methods forEach, map, fold, some, select, invoke, all, reject, max, min, partition, groupBy

Utility Methods

noConflict Maddy.noConflict()

Restores the original value of the global Maddy variable and returns a reference to the Maddy object. noConflict is not available in CommonJS environments, and cannot be invoked more than once.

var Associative = Maddy.noConflict();
typeof Maddy;
// => The type of the original value of the `Maddy` variable.

// `noConflict` can only be invoked once.
typeof Associative.noConflict;
// => "undefined"

typeof Associative.map;
// => "function"

curry Maddy.curry(function, [*arguments])

Partially applies a function, returning a function that, when invoked, calls the original function with the arguments prepended to any additional arguments.

curry can be invoked on itself (i.e., Maddy.curry(Maddy.curry, ...)) to produce a function that, when called, will return a curried function for further partial application. curry can also be used with other higher-order functions, providing a convenient way to invoke a single callback function on multiple objects.

function convert(ratio, symbol, input) {
  return (input * ratio).toFixed(1) + " " + symbol;
}
var pounds = Maddy.curry(convert, 2.2, "lbs.");
pounds(4);
// => "8.8 lbs."

var pints = Maddy.curry(convert, 1.98);
pints("U.S. pints", 2.4);
// => "4.8 U.S. pints"

var meta = Maddy.curry(Maddy.curry, convert);
var kilometers = meta(1.62, "km");
kilometers(34);
// => "55.1 km"

var callback = Maddy.curry(Maddy.forEach, function (key, value) {
  console.log(key + ": " + value);
});
callback({"jdalton": "John-David", "mathias": "Mathias"});
// => Prints `"jdalton: John-David" and "mathias: Mathias"`
callback({"kitcam": "Kit", "maddy": "Maddy"});
// => Prints `"kitcam: Kit" and "maddy: Maddy"`

Generic Methods

isPropertyOf Maddy.isPropertyOf(object, property)

Determines if property is a direct property of the specified object.

function Person(name, age) {
  this.name = name;
  this.age = age;
}
Person.prototype.say = function (message) {
  return this.name + ": " + message;
};

var kit = new Person("Kit", 18);

Maddy.isPropertyOf(kit, "name"); // => true
Maddy.isPropertyOf(kit, "age"); // => true
Maddy.isPropertyOf(kit, "say"); // => false

getClassOf Maddy.getClassOf(object)

Returns the internal [[Class]] name of the provided object. To account for cross-environment inconsistencies, only the following [[Class]] names are supported: Function, Array, Date, RegExp, String, Boolean, and Number.

Note that Null and Undefined are not [[Class]] names, but are implemented for parity with Object::toString.

Maddy.getClassOf(null); // => "Null"
Maddy.getClassOf(); // => "Undefined"

Maddy.getClassOf("Maddy"); // => "String"
Maddy.getClassOf(new String("Maddy")); // => "String"

Maddy.getClassOf(5); // => "Number"
Maddy.getClassOf(new Boolean(true)); // => "Boolean"

Maddy.getClassOf({}); // => "Object"
Maddy.getClassOf([]); // => "Array"
Maddy.getClassOf(function () {}); // => "Function"
Maddy.getClassOf(/(?:)/); // => "RegExp"
Maddy.getClassOf(new Date); // => "Date"

isEqual Maddy.isEqual([left, right])

Recursively compares two objects (left and right) using the following algorithm:

Note: JScript 5, implemented in Internet Explorer 6-8, treats undefined elements in arrays as elisions. Thus, in Internet Explorer, sparse arrays (Array(3)) are equivalent to dense arrays containing undefined values ([undefined, undefined, undefined]).

Maddy.isEqual(0, 0); // => true
Maddy.isEqual(0, -0); // => false
Maddy.isEqual("", 0); // => false
Maddy.isEqual(false, ""); // => false
Maddy.isEqual(null, undefined); // => false

Maddy.isEqual("Kit", new String("Kit")); // => true
Maddy.isEqual(new String("Kit"), new String("Kit")); // => true
Maddy.isEqual(new Number(3), new Number(3)); // => true
Maddy.isEqual(NaN, NaN); // => true

Maddy.isEqual(new Date(2009, 4, 9), new Date(2009, 4, 9)); // => true
Maddy.isEqual(new Date("Kit"), new Date("Kit")); // => false;

Maddy.isEqual(/(?:)/gim, /(?:)/gim); // => true
Maddy.isEqual(/(?:)/gi, /(?:)/gm); // => false
Maddy.isEqual(/Kit/gm, /Maddy/gm); // => false

var elements = [], equivalent = [];
elements.push(elements, 1, new String("Kit"), new Number(3), elements);
equivalent.push(equivalent, new Number(1), new String("Kit"), 3, equivalent);
Maddy.isEqual(elements, equivalent); // => true

elements = {};
equivalent = {};
elements.elements = elements;
equivalent.elements = equivalent;
Maddy.isEqual(elements, equivalent); // => true

stringify Maddy.stringify(object)

Returns a debug-oriented representation of an object, similar to JSON.stringify. Objects are serialized according to a superset of the JSON encoding algorithm:

Note that custom toJSON methods are ignored.

Maddy.stringify([1, 2, 3, "Kit", {"name": "Maddy"}, NaN, Infinity]);
// => "[1, 2, 3, \"Kit\", {\"name\": \"Maddy\"}, null, null]"

Maddy.stringify("Hello\tthis\bis\\nice");
// => "\"Hello\tthis\bis\\nice\""

Maddy.stringify(/(?:)/);
// => "{\"source\": \"(?:)\", \"global\": false, \"ignoreCase\": false, \"multiline\": false}"

Maddy.stringify({"kitcam": [new String("Kit Cambridge"), new Number(18)]});
// => "{\"kitcam\": [\"Kit Cambridge\", 18]}"

Maddy.stringify(new Date(Date.UTC(1994, 06, 03)));
// => "\"1994-07-03T00:00:00.000Z\""

Maddy.stringify();
// => "null"

Maddy.stringify({"value": new Boolean(true), "toJSON": function () {
  return this.value.valueOf();
}});
// => "{\"value\": true, \"toJSON\": null}"

var structure = [];
structure.push(structure, 2, structure, 4, structure);
Maddy.stringify(structure);
// => "[null, 2, null 4, null]"

isEmpty Maddy.isEmpty(object)

Checks if the provided object is empty using the following algorithm:

isEmpty does not distinguish between primitives and their corresponding object wrappers.

Maddy.isEmpty(null); // => true
Maddy.isEmpty(); // => true

Maddy.isEmpty(Infinity); // => true
Maddy.isEmpty(NaN); // => true
Maddy.isEmpty(new Date("Invalid Date")); // => true

Maddy.isEmpty({}); // => true
Maddy.isEmpty([1, 2, 3]); // => false
Maddy.isEmpty(""); // => true
Maddy.isEmpty(/(?:)/); // => false

keys Maddy.keys(object)

Returns a lexicographically-sorted array of an object’s property names.

Maddy.keys({"first": 1, "second": 2, "third": 3});
// => ["first", "second", "third"]

Maddy.keys({"name": "Kit", "age": 18, developer: true, hobbies: ["running", "programming"]});
// => ["age", "developer", "hobbies", "name"]

extend Maddy.extend(destination, *sources)

Extends a destination object with the properties of one or more sources.

Maddy.extend({"name": "Kit"}, {"age": 18}, {"hobbies": ["running", "programming"]});
// => {"name": "Kit", "age": 18, "hobbies": ["running", "programming"]}

Functional Methods

Note: These methods should not be used with arrays or index-based collections, as they are exceedingly inefficient for this purpose. Consider Underscore or Functional instead.

forEach Maddy.forEach(callback, [context], object)

Iterates over an object, invoking a callback function for each iteration. The iteration algorithm is normalized to account for cross-environment inconsistencies, including the JScript [[DontEnum]] bug, the Safari 2 property shadowing bug, and inconsistent enumeration of the prototype and constructor properties.

The callback is bound to the optional context object, if one is specified, and accepts three arguments: (key, value, object).

Maddy.forEach(function (pseudonym, name, object) {
  console.log(pseudonym + ": " + name);
}, {"jdalton": "John-David Dalton", "kitcam": "Kit Cambridge"});
// => Prints `"jdalton: John-David Dalton"` and `"kitcam: Kit Cambridge"`.

map, collect Maddy.map(callback, [context], object)

Returns an object containing the results of invoking a callback function on each object member.

Maddy.map(function (pseudonym, data) {
  return [data.name, data.age];
}, {
  "jdalton": {"name": "John-David", "age": 29},
  "kitcam": {"name": "Kit Cambridge", "age": 18}
});
// => {"jdalton": ["John-David", 29], "kitcam": ["Kit Cambridge", 18]}

fold, inject, reduce Maddy.fold(callback, [context], memo, object)

Reduces an object to a single value by successively invoking the callback function on each member. memo specifies the initial state of the reduction; each nth invocation of callback should return the value of memo to be used in the n+1th invocation.

The callback function accepts four arguments: (memo, key, value, object).

Maddy.fold(function (memo, name, occupation) {
  return memo.push(name, occupation), memo;
}, [], {"kitcam": "programmer", "maddy": "runner"}).sort();
// => ["kitcam", "maddy", "programmer", "runner"]

Maddy.reduce(function (memo, name, year) {
  return memo[year] = name, memo;
}, {}, {"Perl": 1987, "Haskell": 1990, "JavaScript": 1996});
// => {1987: "Perl", 1990: "Haskell", 1996: "JavaScript"}

Maddy.inject(function (memo, name, age) {
  return memo + age;
}, 0, {"jdalton": 29, "kitcam": 18, "maddy": 17});
// => 64

some, any Maddy.some(callback, [context], object)

Determines if the callback returns true for at least one object member.

Maddy.some(function (name, year) {
  return name > "Lua" && year > 1990;
}, {"Haskell": 1990, "JavaScript": 1996, "Python": 1991});
// => true

select, findAll, filter Maddy.select(callback, [context], object)

Returns an object containing all object members for which the callback returns true. reject is the opposite of this method.

Maddy.select(function (name, year) {
  return year > 2007;
}, {"Functional": 2007, "Underscore": 2009, "Maddy": 2011});
// => {"Underscore": 2009, "Maddy": 2011}

Maddy.findAll(function (name, year) {
  return name > "Sencha" && year > 2007;
}, {"Prototype": 2005, "MooTools": 2006, "Underscore": 2009});
// => {"Underscore": 2009}

invoke, send Maddy.invoke(object, method, [*arguments])

Invokes a method with optional arguments on every object member value. invoke does not directly mutate the original object, but the object may be mutated by the method.

Maddy.invoke({1990: "**Haskell**", 1993: "_*Ruby*_"}, "slice", 2, -2);
// => {1990: "Haskell", 1993: "Ruby"}

// Note that the original object may be indirectly mutated by the invoked method.
var developers = {"mathias": ["Mathias Bynens", 23], "kitcam": ["Kit Cambridge", 18]};

Maddy.send(developers, "shift");
// => {"mathias": "Mathias Bynens", "kitcam": "Kit Cambridge"}

developers;
// => {"mathias": [23], "kitcam": [18]}

all, every Maddy.all(callback, [context], object)

Determines whether the callback returns true for all object members.

Maddy.all(function (name, year) {
  return name < "Visual Basic";
}, {"Haskell": 1990, "JavaScript": 1996, "Python": 1991});
// => true

reject Maddy.reject(callback, [context], object)

Returns an object containing all object members for which the callback returns false. select is the opposite of this method.

Maddy.reject(function (name, year) {
  return year > 2010;
}, {"Backbone": 2010, "Underscore": 2009, "Maddy": 2011});
// => {"Backbone": 2010, "Underscore": 2009}

max Maddy.max(callback, [context], object)

Returns the key of the maximum object member-based computation. Members are compared by invoking the callback on each member and comparing successive return values.

Maddy.max(function (pseudonym, data) {
  return data.age;
}, {"jdalton": {"name": "John-David", "age": 29}, "mathias": {"name": "Mathias", "age": 23}});
// => "jdalton"

min Maddy.min(callback, [context], object)

Returns the key of the minimum object member-based computation.

Maddy.min(function (pseudonym, data) {
  return data.age;
}, {"mathias": {"name": "Mathias", "age": 23}, "kitcam": {"name": "Kit", "age": 18}});
// => "kitcam"

partition Maddy.partition(callback, [context], object)

Separates an object’s members into two groups: those for which the callback returns true, and those for which it returns false. partition is preferred to invoking both select and reject on the same object.

Maddy.partition(function (pseudonym, name) {
  return name.length > 5;
}, {"jdalton": "John-David", "mathias": "Mathias", "kitcam": "Kit", "maddy": "Maddy"});
// => [{"jdalton": "John-David", "mathias": "Mathias"}, {"kitcam": "Kit", "maddy": "Maddy"}]

groupBy Maddy.groupBy(callback, [context], object)

Groups an object’s members using the criteria specified by the callback.

Maddy.groupBy(function (name, dish) {
  return dish;
}, {
  "John-David": "lasagna",
  "Mathias": "sushi",
  "Maddy": "lasagna",
  "Kit": "soup",
  "Sam": "cinnamon rolls",
  "Joe": "sushi",
  "Mark": "sushi"
});

/* => {
  "lasagna": {"John-David": "lasagna", "Maddy": "lasagna"},
  "sushi": {"Mathias": "sushi", "Joe": "sushi", "Mark": "sushi"},
  "soup": {"Kit": "soup"},
  "cinnamon rolls": {"Sam": "cinnamon rolls"}
} */