MochiKit.Base - functional programming and useful comparisons
myObjectRepr = function () { // gives a nice, stable string representation for objects, // ignoring any methods var keyValuePairs = []; for (var k in this) { var v = this[k]; if (typeof(v) != 'function') { keyValuePairs.push([k, v]); } }; keyValuePairs.sort(compare); return "{" + map( function (pair) { return map(repr, pair).join(":"); }, keyValuePairs ).join(", ") + "}"; }; // repr() will look for objects that have a repr method myObjectArray = [ {"a": 3, "b": 2, "repr": myObjectRepr}, {"a": 1, "b": 2, "repr", myObjectRepr} ]; // sort it by the "a" property, check to see if it matches myObjectArray.sort(keyComparator("a")); expectedRepr = '[{"a": 1, "b": 2}, {"a": 3, "b": 2}]'; assert( repr(myObjectArray) == expectedRepr ); // get just the "a" values out into an array sortedAValues = map(itemgetter("a"), myObjectArray); assert( compare(sortedAValues, [1, 3]) == 0 );
MochiKit.Base is the foundation for the MochiKit suite. It provides:
Python users will feel at home with MochiKit.Base, as the facilities are quite similar to those available as part of Python and the Python standard library.
None.
The comparators (operators for comparison) in JavaScript are deeply broken, and it is not possible to teach them new tricks.
The extensible comparison facility exposed by MochiKit as a simple compare(a, b) function, which should be used in lieu of JavaScript's operators whenever comparing objects other than numbers or strings (though you can certainly use compare for those, too!).
The compare function has the same signature and return value as a sort function for Array.prototype.sort, and is often used in that context.
Defining new comparators for the compare function to use is done by adding an entry to its AdapterRegistry with the registerComparator function.
JavaScript's default representation mechanism, toString, is notorious for having terrible default behavior. It's also very unwise to change that default, as other JavaScript code you may be using may depend on it.
It's also useful to separate the concept of a "string representation" and a "string representation for programmers", much like Python does with its str and repr protocols.
repr provides this programmer representation for JavaScript, in a way that doesn't require object prototype hacking: using an AdapterRegistry. Objects that implement the repr protocol can either implement a .repr() or .__repr__() method, or they can simply have an adapter setup to generate programmer representations. By default, the registry provides nice representations for null, undefined, Array, and objects or functions with a NAME attribute that use the default toString. For objects that repr doesn't already understand, it simply defaults to toString, so it will integrate seamlessly with code that implements the idiomatic JavaScript toString method!
To define a programmer representation for your own objects, simply add a .repr() or .__repr__() method that returns a string. For objects that you didn't create (e.g., from a script you didn't write, or a built-in object), it is instead recommended that you create an adapter with registerRepr.
MochiKit makes extensive use of adapter registries, which enable you to implement object-specific behaviors for objects that you do not necessarily want to modify, such as built-in objects. This is especially useful because JavaScript does not provide a method for hiding user-defined properties from for propName in obj enumeration.
AdapterRegistry is simply an encapsulation for an ordered list of "check" and "wrap" function pairs. Each AdapterRegistry instance should perform one function, but may have multiple ways to achieve that function based upon the arguments. One way to think of it is as a poor man's generic function, or multiple dispatch (on arbitrary functions, not just type!).
Check functions take one or more arguments, and return true if the argument list is suitable for the wrap function. Check functions should perform "cheap" checks of an object's type or contents, before the "expensive" wrap function is called.
Wrap functions take the same arguments as check functions and do some operation, such as creating a programmer representation or comparing both arguments.
Much of MochiKit.Base is there to simply remove the grunt work of doing generic JavaScript programming.
Need to take every property from one object and set them on another? No problem, just call update(dest, src)! What if you just wanted to update keys that weren't already set? Look no further than setdefault(dest, src[, ...]).
Want to return a mutable object, but don't want to suffer the consequences if the user mutates it? Just clone(it) and you'll get a copy-on-write clone. Cheaper than a copy!
Need to extend an Array with another array? Or even an array-like object such as a NodeList or the special arguments object? Even if you need to skip the first few elements of the source array-like object, it's no problem with extend(dstArray, srcArrayLike[, skip])!
Wouldn't it be convenient to have all of the JavaScript operators were available as functions somewhere? That's what the operators table is for, and it even comes with additional operators based on the compare function.
Need to walk some tree of objects and manipulate or find something in it? A DOM element tree perhaps? Use nodeWalk(node, visitor)!
There's plenty more, so check out the API Reference below.
Functional programming constructs such as map and filter can save you a lot of time, because JavaScript iteration is error-prone and arduous. Writing less code is the best way to prevent bugs, and functional programming can help you do that.
MochiKit.Base ships with a few simple Array-based functional programming constructs, namely map and filter, and their "extended" brethren, xmap and xfilter.
map(func, arrayLike[, ...]) takes a function and an array-like object, and creates a new Array. The new Array is the result of func(element) for every element of arrayLike, much like the Array.prototype.map extension in Mozilla. However, MochiKit.Base takes that a step further and gives you the full blown Python version of map, which will take several array-like objects, and calls the function with one argument per given array-like, like this:
var arrayOne = [1, 2, 3, 4, 5]; var arrayTwo = [1, 5, 2, 4, 3]; var arrayThree = [5, 2, 1, 3, 4]; var biggestElements = map(objMax, arrayOne, arrayTwo, arrayThree); assert( objEqual(biggestElements, [5, 5, 3, 4, 5]) );
filter(func, arrayLike[, self]) takes a function and an array-like object, and returns a new Array. This is basically identical to the Array.prototype.filter extension in Mozilla. self, if given, will be used as this in the context of func when called.
xmap and xfilter are just special forms of map and filter that accept a function as the first argument, and use the extra arguments as the array-like. Not terribly interesting, but a definite time-saver in some cases.
If you appreciate the functional programming facilities here, you should definitely check out MochiKit.Iter, which provides for full blown iterators, range, reduce, and a near-complete port of Python's itertools module, with some extra stuff thrown in for good measure!
JavaScript's method-calling special form and lack of bound functions (functions that know what this should be) are one of the first stumbling blocks that programmers new to JavaScript face. The bind(func, self) method fixes that right up by returning a new function that calls func with the right this.
In order to take real advantage of all this fancy functional programming stuff, you're probably going to want partial application. This allows you to create a new function from an existing function that remembers some of the arguments. For example, if you wanted to compare a given object to a slew of other objects, you could do something like this:
compareWithOne = partial(compare, 1); results = map(compareWithOne, [0, 1, 2, 3]); assert( objEqual(results, [-1, 0, 1, 1]) );
One of the better uses of partial functions is in MochiKit.DOM, which is certainly a must-see for those of you creating lots of DOM elements with JavaScript!
NamedError:
Convenience constructor for creating new errors (e.g. NotFound)
AdapterRegistry:
A registry to facilitate adaptation.
All check/wrap functions in this registry should be of the same arity.
AdapterRegistry.prototype.register(name, check, wrap[, override]):
The check function should return true if the given arguments are appropriate for the wrap function.
If override is given and true, the check function will be given highest priority. Otherwise, it will be the lowest priority adapter.
AdapterRegistry.prototype.match(obj[, ...]):
Find an adapter for the given arguments.
If no suitable adapter is found, throws NotFound.
AdapterRegistry.prototype.unregister(name):
Remove a named adapter from the registry
clone(obj):
Return a new object using obj as its prototype. Use this if you want to return a mutable object (e.g. instance state), but don't want the user to mutate it. If they do, it won't have any effect on the original obj.
Note that this is a shallow clone, so mutable properties will have to be cloned separately if you want to "protect" them.
extend(self, obj[, skip]):
Mutate an array by extending it with an array-like obj, starting with the "skip" index of obj. If null is given as the initial array, a new one will be created.
This mutates and returns the given array, be warned.
update(self, obj[, ...]):
Mutate an object by replacing its key:value pairs with those from other object(s). Key:value pairs from later objects will overwrite those from earlier objects.
If null is given as the initial object, a new one will be created.
This mutates and returns the given object, be warned.
A version of this function that creates a new object is available as merge(a, b[, ...])
merge(obj[, ...]):
Create a new instance of Object that contains every property from all given objects. If a property is defined on more than one of the objects, the last property is used.
This is a special form of update(self, obj[, ...]), specifically, it is defined as partial(update, null).
setdefault(self, obj[, ...]):
Mutate an object by adding all properties from other object(s) that it does not already have set.
If self is null, a new Object instance will be created and returned.
This mutates and returns the given self, be warned.
keys(obj):
Return an Array of the property names of an object (in the order determined by for propName in obj).
items(obj):
Return an Array of [propertyName, propertyValue] pairs for the given obj (in the order deterined by for propName in obj).
operator:
A table of JavaScript's operators for usage with map, filter, etc.
Unary Logic Operators:
Operator Implementation Description truth(a) !!a Logical truth lognot(a) !a Logical not identity(a) a Logical identity Unary Math Operators:
Operator Implementation Description not(a) ~a Bitwise not neg(a) -a Negation Binary Operators:
Operator Implementation Description add(a, b) a + b Addition div(a, b) a / b Division mod(a, b) a % b Modulus and(a, b) a & b Bitwise and or(a, b) a | b Bitwise or xor(a, b) a ^ b Bitwise exclusive or lshift(a, b) a << b Bitwise left shift rshift(a, b) a >> b Bitwise signed right shift zrshfit(a, b) a >>> b Bitwise unsigned right shift Built-in Comparators:
Operator Implementation Description eq(a, b) a == b Equals ne(a, b) a != b Not equals gt(a, b) a > b Greater than ge(a, b) a >= b Greater than or equal to lt(a, b) a < b Less than le(a, b) a <= b Less than or equal to Extended Comparators (uses compare):
Operator Implementation Description ceq(a, b) compare(a, b) == 0 Equals cne(a, b) compare(a, b) != 0 Not equals cgt(a, b) compare(a, b) == 1 Greater than cge(a, b) compare(a, b) != -1 Greater than or equal to clt(a, b) compare(a, b) == -1 Less than cle(a, b) compare(a, b) != 1 Less than or equal to Binary Logical Operators:
Operator Implementation Description logand(a, b) a && b Logical and logor(a, b) a || b Logical or contains(a, b) b in a Has property (note order)
forward(name):
Returns a function that forwards a method call to this.name(...)
itemgetter(name):
Returns a function(obj) that returns obj[name]
typeMatcher(typ[, ...]):
Given a set of types (as string arguments), returns a function(obj[, ...]) that will return true if the types of the given arguments are all members of that set.
isNull(obj[, ...]):
Returns true if all arguments are null.
isUndefinedOrNull(obj[, ...]):
Returns true if all arguments are undefined or null
isNotEmpty(obj[, ...]):
Returns true if all the given Array-like or string arguments are not empty (obj.length > 0)
isArrayLike(obj[, ...]):
Returns true if all given arguments are Array-like (have a .length property and typeof(obj) == 'object')
isDateLike(obj[, ...]):
Returns true if all given arguments are Date-like (have a .getTime() method)
xmap(fn, obj[, ...):
Return a new Array composed of fn(obj) for every obj given as an argument.
If fn is null, operator.identity is used.
map(fn, lst[, ...]):
Return a new array composed of the results of fn(x) for every x in lst.
If fn is null, and only one sequence argument is given the identity function is used.
map(null, lst) -> lst.slice();If fn is null, and more than one sequence is given as arguments, then the Array function is used, making it equivalent to zip.
- map(null, p, q, ...)
- -> zip(p, q, ...) -> [[p0, q0, ...], [p1, q1, ...], ...];
xfilter(fn, obj[, ...]):
Returns a new Array composed of the arguments where fn(obj) returns a true value.
If fn is null, operator.truth will be used.
filter(fn, lst):
Returns a new Array composed of all elements from lst where fn(lst[i]) returns a true value.
If fn is null, operator.truth will be used.
bind(func, self[, arg, ...]):
Return a copy of func bound to self. This means whenever and however the returned function is called, this will always reference the given self.
Calling bind(func, self) on an already bound function will return a new function that is bound to the new self! If self is undefined, then the previous self is used. If self is null, then the this object is used (which may or may not be the global object). To force binding to the global object, you should pass it explicitly.
Additional arguments, if given, will be partially applied to the function. These three expressions are equivalent and return equally efficient functions (bind and partial share the same code path):
- ``bind(oldfunc, self, arg1, arg2)`` - ``bind(partial(oldfunc, arg1, arg2), self)`` - ``partial(bind(oldfunc, self), arg1, arg2)``
bindMethods(self):
Bind all methods of self present on self to self, which gives you a semi-Pythonic sort of instance.
registerComparator(name, check, comparator[, override]):
Register a comparator for use with compare.
name should be a unique identifier describing the comparator.
check is a function(a, b) that returns true if a and b can be compared with comparator.
comparator is a function(a, b) that returns:
Value Condition 0 a == b 1 a > b -1 a < b comparator is guaranteed to only be called if check(a, b) returns a true value.
If override is true, then it will be made the highest precedence comparator. Otherwise, the lowest.
compare(a, b):
Compare two objects in a sensible manner. Currently this is:
- undefined and null compare equal to each other
- undefined and null are less than anything else
- If JavaScript says a == b, then we trust it
- comparators registered with registerComparator are used to find a good comparator. Built-in comparators are currently available for Array-like and Date-like objects.
- Otherwise hope that the built-in comparison operators do something useful, which should work for numbers and strings.
- If neither a < b or a > b, then throw a TypeError
Returns what one would expect from a comparison function:
Value Condition 0 a == b 1 a > b -1 a < b
registerRepr(name, check, wrap[, override]):
Register a programmer representation function. repr functions should take one argument and return a string representation of it suitable for developers, primarily used when debugging.
If override is given, it is used as the highest priority repr, otherwise it will be used as the lowest.
repr(o):
Return a programmer representation for an object. See the Programmer Representation overview for more information about this function.
objEqual(a, b):
Compare the equality of two objects.
arrayEqual(self, arr):
Compare two arrays for equality, with a fast-path for length differences.
concat(lst[, ...]):
Concatenates all given array-like arguments and returns a new Array:
var lst = concat(["1","3","5"], ["2","4","6"]); assert( lst.toString() == "1,3,5,2,4,6" );
keyComparator(key[, ...]):
A comparator factory that compares a[key] with b[key]. e.g.:
var lst = ["a", "bbb", "cc"]; lst.sort(keyComparator("length")); assert( lst.toString() == "a,cc,bbb" );
reverseKeyComparator(key):
A comparator factory that compares a[key] with b[key] in reverse. e.g.:
var lst = ["a", "bbb", "cc"]; lst.sort(reverseKeyComparator("length")); assert(lst.toString() == "bbb,cc,aa");
partial(func, arg[, ...]):
Return a partially applied function, e.g.:
addNumbers = function (a, b) { return a + b; } addOne = partial(addNumbers, 1); assert(addOne(2) == 3);partial is a special form of bind that does not alter the bound self (if any). It is equivalent to calling:
bind(func, undefined, arg[, ...]);See the documentation for bind for more details about this facility.
NOTE: This could be used to implement, but is NOT currying.
listMinMax(which, lst):
If which == -1 then it will return the smallest element of the Array-like lst. This is also available as listMin(lst).
If which == 1 then it will return the largest element of the array-like lst. This is also available as listMax(list).
listMin(lst):
Return the smallest element of an Array-like object, as determined by compare. This is a special form of listMinMax, specifically partial(listMinMax, -1).
listMax(lst):
Return the largest element of an Array-like object, as determined by compare. This is a special form of listMinMax, specifically partial(listMinMax, 1).
objMax(obj[, ...]):
Return the maximum object out of the given arguments. This is similar to listMax, except is uses the arguments instead of a given Array-like.
objMin(obj[, ...]):
Return the minimum object out of the given arguments. This is similar to listMin, except it uses the arguments instead of a given Array-like.
nodeWalk(node, visitor):
Non-recursive generic node walking function (e.g. for a DOM)
- node:
- The initial node to be searched.
- visitor:
- The visitor function, will be called as visitor(node), and should return an Array-like of nodes to be searched next (e.g. node.childNodes).
nameFunctions(namespace):
Given a namespace with a NAME property, find all functions in it and give them nice NAME properties too (for use with repr). e.g.:
namespace = { NAME: "Awesome", Dude: function () {} } nameFunctions(namespace); assert( namespace.Dude.NAME == 'Awesome.Dude' );
Copyright 2005 Bob Ippolito <bob@redivi.com>. This program is free software; you can redistribute it and/or modify it under the terms of the MIT License.