f_underscore.js

f_underscore.js is a library of functions that create and compose iterator functions. It is designed to be used in conjunction with underscore.js's functions that employ iterators, functions like filter and map. With the f_underscore.js library you can wield and weld iterator functions declaratively and dynamically. It's the tie clip to go along with underscore.js's tie.

The project is hosted on GitHub and available under the MIT license. The complete test suite and annotated source code are available here.

Download

Development Version (0.2.1) 12kb, Uncompressed with Comments
Production Version (0.2.1)<1.5kb, Minified and Gzipped
Production w/ Underscore 1.3.3<5kb, Minified and Gzipped
(Note: Depends on underscore.js)

Code Sampler

// Basic
_.map([1, 2, 3], f_.multiply(3));
=> [3, 6, 9]
_.filter([1, 2, 3], f_.greaterThan(2));
=> [3]
_.sortBy([{name:'moe'}, {name:'larry'}, {name:'curly'}], f_.get('name'));
=> [{name:'curly'}, {name:'larry'}, {name:'moe'}]

// Chaining
_.filter([1, 2, 3, 4, 5, 6], f_().modulo(2).isEqual(0));
=> [2, 4, 6]
var stooges = [{name:'moe'}, {name:'larry'}, {name:'curly'}];
_.map(stooges, f_().get('name').substr(0,1).toUpperCase());
=> ['M', 'L', 'C']

// Composition
var data =          [ {name:'Diane', age:21, sex:'f'},
                      {name:'Jack',  age:20, sex:'m'},
                      {name:'Moe',   age:40, sex:'m'} ];
var userCriteria =  [ [ 'sex', 'isEqual',     'm' ],
                      [ 'age', 'greaterThan', 20  ] ];
var filterFn  = _.reduce(userCriteria,
                        function(memo, rule) {
                            var prop = rule[0], expr = rule[1], val = rule[2];
                            return f_.and(memo, f_[expr](f_.get(prop), val));
                        }, true);
_.filter(data, filterFn);
=> [{name:'Moe',  age:40, sex:'m'}]

Accessors

get f_.get(property) => iterator(object)
Returns an iterator function that will return the value of object[property] when called.

var getter = f_.get('flavor');
getter({flavor:'Vanilla', size:'Large'});
=> 'Vanilla'
var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.get('name'));
=> ['moe', 'larry', 'curly']
_.map(stooges, f_.toUpperCase(f_.get('name')));
=> ['MOE', 'LARRY', 'CURLY']

getByProperty f_.getByProperty(object) => iterator(property)
Returns an iterator function that will return the value of object[property] when called.

var iceCream = {flavor:'Vanilla', size:'Large', cone:'Waffle'};
var getProperty = f_.getByProperty(iceCream);
getProperty('flavor');
=> 'Vanilla'
_.map(['cone', 'flavor'], f_.getByProperty(iceCream));
=> ['Waffle', 'Vanilla']

project f_.project(propertyNameArray) => iterator(object)
Returns an iterator function that will create and return a new object with properties in the propertyNameArray whose values are copied from object.

var iceCream = {flavor:'Vanilla', size:'Large', cone:'Waffle'};
var projectFn = f_.project(['cone', 'flavor']);
projectFn(iceCream);
=> {cone:'Waffle', flavor:'Vanilla'}

getSet f_.getSet(property, fn) => iterator(object)
Returns an iterator function that will set object[property] to the return value of fn(object[property]) and then returns object.

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
var incrementAge = f_.getSet('age', function(ageVal) { return ageVal + 1; });
incrementAge(stooges[0]);
=> {name:'moe', age:41}
_.map(stooges, f_.getSet('age', f_.incr()));
=> [{name:'moe', age:42}, {name:'larry', age:51}, {name:'curly', age:61}]

set f_.set(property, f_val) => iterator(object)
Returns an iterator function that will set the value of object[property] to the evaluation of f_val and then returns object.

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
var setter = f_.set('age', 41);
setter(stooges[0]);
=> {name:'moe', age:41}
_.map(stooges, f_.set('age', f_.incr(f_.get('age'))));
=> [{name:'moe', age:42}, {name:'larry', age:51}, {name:'curly', age:61}]

setByProperty f_.setByProperty(object, f_val) => iterator(property)
Returns an iterator function that will set the value of object[property] to the evaluation of f_val. The resulting iterator will return object.

var player = {name:'Kendall Marshall', points:22, assists:13, turnovers: 1};
var reset = f_.setByProperty(player, 0);
reset('points');
=> {name:'Kendall Marshall', points:0, assists:13, turnovers: 1}
_.map(['assists', 'turnovers'], reset);
=> {name:'Kendall Marshall', points:0, assists:0, turnovers: 0}

Expressions

unary expressions f_.unaryExpr([f_val]) => iterator(item)
Unary expressions have an optional f_val parameter. If f_val is not provided it defaults to the identity function. Unary functions in f_underscore.js include increment, decrement, square, negate, and not.

_.map([1, 2, 3], f_.negate())
=> [-1, -2, -3]
_.map([{a:1}, {a:2}, {a:3}], f_.negate(f_.get('a')));
=> [-1, -2, -3]

binary exprs f_.binaryExpr([f_val_left,] f_val_right) => iterator(item)
Binary expressions have an optional f_val_left. When f_val_left is not provided it defaults to the identity function. Binary functions in f_underscore.js include isEqual, isNotEqual, greaterThan, atLeast, lessThan, atMost, and, neither, or, xor, add, subtract, multiply, divide, modulo, append, and prepend.

_.map([1, 2, 3], f_.multiply(3))
=> [3, 6, 9]
_.map([{a:1, b:2}, {a:2, b:4}], f_.multiply(f_.get('a'), f_.get('b')));
=> [2, 4]

predicate 'truth test' iterators f_.expr() => predicate(item) => boolean
Truth test iterator functions, or predicates, are returned by f_underscore.js functions not, isEqual, isNotEqual, greaterThan, atLeast, lessThan, atMost, and, neither, or, and xor. Predicate iterators will always return true or false. They're handy when paired with underscore.js functions such as _.find, _.filter, _.reject, _.all, and _.any.

f_val parameters - "function or value"
Most f_underscore.js functions have parameters named f_val or f_val_suffix. These are used and closed over by the iterators f_underscore.js creates. These parameters accept any value, including functions. When f_val is a function, it is evaluated at the time the iterator function is called, being passed the iterator's item. It is an iterator called recursively within an iterator. This enables us to nest f_underscore.js function calls anywhere an f_val is expected.

var plus1 = function(f_val) {
    var iterator = function(item) {
        if(_.isFunction(f_val) {
            return f_val(item) + 1;
        } else {
            return f_val + 1;
        }
    };
    return iterator;
};
var plus1and1 = plus1(1);
plus1and1();
=> 2
var aPlus1 = plus1(function(item) { return item.a; });
aPlus1({a:10});
=> 11
var aPlus1_idiomatic = plus1(f_.get('a'));
aPlus1_idiomatic({a:10});
=> 11

Equality

isEqual f_.isEqual([f_val_L,] f_val_R) => predicate(item) Alias: eq
Deep equality test comparing left and right f_vals. isEqual is a binary expression that creates and returns a predicate iterator function. Uses _.isEqual to perform the deep equality comparison.

var isEqualTo3 = f_.isEqual(3);
isEqualTo3(1);
=> false
_.filter([1, 2, 3], f_.eq(3));
=> [3]

var nameIsCurly = f_.isEqual(f_.get('name'), 'curly');
nameIsCurly({name:'moe'});
=> false
_.filter([{name:'moe'}, {name:'larry'}, {name:'curly'}], nameIsCurly);
=> [{name:'curly'}]

isNotEqual f_.isNotEqual([f_val_L,] f_val_R) => predicate(item) Alias: neq
Deep inequality test comparing left and right f_vals. isEqual is a binary expression that creates and returns a predicate iterator function. Uses _.isEqual to perform the deep equality comparison.

var isNotEqualTo3 = f_.isNotEqual(3);
isNotEqualTo3(1);
=> true 
_.filter([1, 2, 3], f_.neq(3));
=> [1, 2]

var nameIsNotCurly = f_.isNotEqual(f_.get('name'), 'curly');
nameIsNotCurly({name:'moe'});
=> true
_.filter([{name:'moe'}, {name:'larry'}, {name:'curly'}], nameIsNotCurly);
=> [{name:'moe'}, {name:'larry'}]

Relational

greaterThan f_.greaterThan([f_val_L,] f_val_R) => predicate(item) Alias: gt
Greater than test comparing left and right f_vals. It is a binary expression that creates and returns a predicate iterator function.

var greaterThan2 = f_.greaterThan(2);
greaterThan2(2);
=> false
_.filter([1, 2, 3], f_.gt(2));
=> [3]

var olderThan40 = f_.greaterThan(f_.get('age'), 40);
olderThan40({ name:'moe', age:40});
=> false 
var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.filter(stooges, olderThan40);
=> [{name:'larry', age:50}, {name:'curly', age:60}]

atLeast f_.atLeast([f_val_L,] f_val_R) => predicate(item) Alias: gte
At least is a greater than or equal to test comparing left and right f_vals. It is a binary expression that creates and returns a predicate iterator function.

var atLeast2 = f_.atLeast(2);
atLeast2(2);
=> true
_.filter([1, 2, 3], f_.gte(2));
=> [2, 3]

var atLeast50 = _.atLeast(f_.get('age'), 50);
atLeast50({name:'moe', age:40});
=> false 
var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.filter(stooges, atLeast50);
=> [{name:'larry', age:50}, {name:'curly', age:60}]

lessThan f_.lessThan([f_val_L,] f_val_R) => predicate(item) Alias: lt
Greater than test comparing left and right f_vals. It is a binary expression that creates and returns a predicate iterator function.

var lessThan2 = f_.lessThan(2);
lessThan2(2);
=> false
_.filter([1, 2, 3], f_.lt(2));
=> [1]

var youngerThan50 = f_.lessThan(f_.get('age'), 50);
youngerThan50({name:'moe', age:40});
=> true
var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.filter(stooges, youngerThan50);
=> [{name:'moe', age:40}]

atMost f_.atMost([f_val_L,] f_val_R) => predicate(item) Alias: lte
At most is a less than or equal to test comparing left and right f_vals. It is a binary expression that creates and returns a predicate iterator function.

var atMost2 = f_.atMost(2);
atMost2(2);
=> true
_.filter([1, 2, 3], f_.lte(2));
=> [1, 2]

var atMost50 = _.atMost(f_.get('age'), 50);
atMost50({name:'moe', age:40});
=> true 
var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.filter(stooges, atMost50);
=> [{name:'moe', age:40}, {name:'larry', age:50}]

Logical

and f_.and([f_val_L,] f_val_R) => predicate(item)
Logical 'and' test comparing truthiness of left and right f_vals. Returns true if both left and right f_vals are true. It is a binary expression that creates and returns a predicate iterator function.

_.filter([1, 2, 3, 4, 5, 6], f_.and(f_.gt(2), f_.lt(5)));
=> [3, 4]

neither f_.neither([f_val_L,] f_val_R) => predicate(item)
Logical 'neither' test comparing truthiness of left and right f_vals. Returns true if neither left nor right f_vals are true. It is a binary expression that creates and returns a predicate iterator function.

_.filter([1, 2, 3, 4, 5], f_.neither(f_.eq(3), f_.eq(5)));
=> [1, 2, 4, 6]

or f_.or([f_val_L,] f_val_R) => predicate(item)
Logical 'or' test comparing truthiness of left and right f_vals. Returns true if either left nor right f_vals are true. It is a binary expression that creates and returns a predicate iterator function.

_.filter([1, 2, 3, 4, 5], f_.or(f_.eq(3), f_.eq(5));
=> [3, 5]

xor f_.xor([f_val_L,] f_val_R) => predicate(item)
Logical 'exclusive or' test comparing truthiness of left and right f_vals. Returns true if only one of left or right f_vals is true and the other is false. It is a binary expression that creates and returns a predicate iterator function.

_.filter([1, 2, 3, 4, 5], f_.xor(f_.eq(2), f_.lt(4)));
=> [1, 3]

not f_.not([f_val]) => predicate(item)
Logical 'not' operator that returns the opposite of the truthiness of its f_val. It is a unary expression that creates and returns a predicate iterator function.

_.filter([1, 2, 3, 4, 5], f_.not(f_.isEqual(3)));
=> [1, 2, 4, 5]

ternary f_.ternary([f_val], f_val_true, f_val_false) => predicate(item)
Logical 'tertiary' operator that evaluetes the truthiness of its f_val. It is a unary expression returns f_val_true if true, or f_val_false if false.

_.map([1, 2, 3, 4, 5], f_.ternary(f_.modulo(2), 'odd', 'even'));
=> ['odd', 'even', 'odd', 'even', 'odd']

Arithmetic

add f_.add([f_val_L,] f_val_R) => iterator(item)
Add two numeric f_vals with this binary expression that creates and returns an iterator.

_.map([1, 2, 3], f_.add(2));
=> [3, 4, 5]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.add(f_.get('age'), 2));
=> [42, 52, 62]

subtract f_.subtract([f_val_L,] f_val_R) => iterator(item) Alias: sub
Subtract the right numeric f_val from the left numeric f_val with this binary expression that creates and returns an iterator.

_.map([1, 2, 3], f_.subtract(2));
=> [-1, 0, 1]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.sub(f_.get('age'), 2));
=> [38, 48, 58]

multiply f_.multiply([f_val_L,] f_val_R) => iterator(item) Alias: mul
Multiply the numeric f_vals with this binary expression that creates and returns an iterator.

_.map([1, 2, 3], f_.multiply(2));
=> [2, 4, 6]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.mul(f_.get('age'), 2));
=> [80, 100, 120]

divide f_.multiply([f_val_L,] f_val_R) => iterator(item) Alias: div
Divide the left f_val by the right f_val with this binary expression that creates and returns an iterator.

_.map([1, 2, 3], f_.divide(2));
=> [0.5, 1, 1.5]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.div(f_.get('age'), 2));
=> [20, 25, 30]

modulo f_.modulo([f_val_L,] f_val_R) => iterator(item) Alias: mod
The remainder of integer division of the left f_val by the right f_val with this binary expression that creates and returns an iterator.

_.map([1, 2, 3, 4], f_.mod(2));
=> [1, 0, 1, 0]

var evens = _.filter([1, 2, 3, 4], f_.eq(f_.mod(2), 0));
=> [2, 4]

increment f_.increment([f_val]) => iterator(item) Alias: incr
Increment a numeric f_val with this unary expression.

_.map([1, 2, 3, 4], f_.increment());
=> [2, 3, 4, 5]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.incr(f_.get('age')));
=> [41, 51, 61]

decrement f_.decrement([f_val]) => iterator(item) Alias: decr
Decrement a numeric f_val with this unary expression.

_.map([1, 2, 3, 4], f_.decrement());
=> [0, 1, 2, 3]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.decr(f_.get('age')));
=> [39, 49, 59]

square f_.square([f_val]) => iterator(item) Alias: sqr
Square a numeric f_val with this unary expression.

_.map([1, 2, 3, 4], f_.square());
=> [1, 4, 9, 16]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.sqr(f_.get('age')));
=> [1600, 2500, 3600]

negate f_.negate([f_val]) => iterator(item) Alias: neg
Negate a numeric f_val with this unary expression.

_.map([1, 2, 3, 4], f_.negate());
=> [-1, -2, -3, -4]

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.map(stooges, f_.neg(f_.get('age')));
=> [-40, -50, -60]

String Methods

f_.stringMethod([f_val_string,] [*f_val_argument]) => iterator(item|string)

The f_underscore.js API aliases String's native methods to create simple, concise string manipulation iterators. The first argument to string methods is an optional f_val that should return the string the method will be applied to if the iterator is called with a non-string. Subsequent arguments follow the same signature as you would pass to the method itself. String methods include: charAt, charCodeAt, concat, fromCharCode, indexOf, lastIndexOf, match, replace, search, slice, split, substr, substring, toLowerCase, toUpperCase.

var toUpper = f_.toUpperCase();
toUpper('larry');
=> LARRY

var nameToUpper = f_.toUpperCase(f_.get('name'));
nameToUpper({name:'larry'});
=> LARRY

var splitSpaces = f_.split(' ');
splitSpaces('larry curly moe');
=> ['larry', 'curly', 'moe']

var replaceSpacesInName = f_.replace(f_.get('name'), / /g, '');
replaceSpacesInName({name:'l a r r y'});
=> larry

Array Methods

f_.arrayMethod([f_val_string,] [*f_val_argument]) => iterator(item|string)

The f_underscore.js API aliases Array's native methods to create simple, concise array manipulation iterators. The first argument to string methods is an optional f_val that should return the string the method will be applied to if the iterator is called with a non-string. Subsequent arguments follow the same signature as you would pass to the method itself. Array methods include: concat, indexOf, join, lastIndexOf, pop, push, reverse, shift, slice, sort, splice, unshift.

var joinBySlash = f_.join('/');
joinBySlash(['a','sample','path']);
=> 'a/sample/path'

Utility Functions

functionize f_.functionize(value) => iterator(item) => value
If value is not a function, create and return an iterator that returns value when called, else return the value function.

var aStringFn = f_.functionize('aString');
aStringFn();
=> 'aString'
var anotherStringFn = f_.functionize(aStringFn);
anotherStringFn();
=> 'aString'
aStringFn === anotherStringFn
=> true

partial f_.partial(function, [*arguments])
Partial application of function that creates and returns a new function that calls function with the given arguments prefilled. Like underscore.js's bind, except does not specify context.

var logMessage = function(label, message) {
    console.log(label + ": " + message);
}
logMessage("Warning", "Example message.");
=> "Warning: Example message."

var logWarning = f_.partial(logMessage, "Warning");
logWarning("Another example message.");
=> "Warning: Another example message."

thread f_.thread(*functions|functionArray) => iterator(item)
Given many functions provided as arguments, or a single array of functions, returns an iterator that will call each function in succession. The first function will be called with item. Each function thereafter will be called with the return value of the previous function called. The thread function is similar to _.compose except that thread calls functions in the same order they are passed. The chaining functionality in f_underscore.js relies on threading.

var canWithdraw20Dollars = f_.thread(
                f_.get('balance'),
                f_.subtract(20),
                f_.greaterThan(0)
             );
canWithdraw20Dollars({name: 'moe', balance: 15});
=> false
canWithdraw20Dollars({name: 'curly', balance: 25});
=> true

zipObject f_.zipObject(keysArray,valuesArray) => object
Given two arrays of same length, one with strings/keys, the other with values, return an object comprised of the keys and values.

f_.zipObject(['a','b'], [1, 2]);
=> {a:1, b:2}

Chaining

f_([propertyName]).function().function()... => iterator(item)

The chaining API provides a concise, readable means for constructing iterator functions. Chaining begins by calling f_ as a function. If the optional propertyName is provided, the first chained call is get(propertyName). Calls can be chained to any f_underscore function thereafter. The chained iterators are queued onto an array. When the resulting iterator is called with an item the first chained iterator is called with item. Each chained iterator thereafter is called with the return value of the previous one. Chaining provides a fluent interface around the f_.thread function.

var canWithdraw20Dollars = f_().get('balance').subtract(20).greaterThan(0);

canWithdraw20Dollars({name: 'moe', balance: 15});
=> false
canWithdraw20Dollars({name: 'curly', balance: 25});
=> true

var stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
_.filter(stooges, f_('age').gt(40));
=> [{name:'larry', age:50}, {name:'curly', age:60}]

Changelog

v0.2.1 - 7/19/2012

v0.2.0 - 6/16/2012

v0.1.0 - 2/28/2012