allong.es

Combinators and Function Decorators

allong.es

The allong.es library is a collection of functions designed to facilitate writing JavaScript and/or CoffeeScript with functions as first-class values. The emphasis in allong.es is on composing and decomposing functions using combinators and decorators. allong.es is designed to complement libraries like Underscore, not compete with them.

At the heart of allong.es are the functions that curry and partially apply other functions. The two most important to understand are call and apply. They work very much like the .call and .apply methods that every JavaScript function implements:

function greet (howwhom) {
  return '' + how + '' + whom + '!';
};
  
call(greet, 'Hello', 'Tom')
  //=> 'Hello, Tom!' 
  
apply(greet, ['Hello', 'Tom'])
  //=> 'Hello, Tom!' 

Their "special sauce" is that they automatically curry the supplied function, so if you provide fewer or no arguments, you get back a partially applied or curried function:

call(greet)('Hello')('Tom')
  //=> 'Hello, Tom!' 
  
call(greet, 'Hello')('Tom'])
  //=> 'Hello, Tom!' 
  
apply(greet, [])('Hello')('Tom')
  //=> 'Hello, Tom!' 
  
apply(greet, ['Hello'])('Tom'])
  //=> 'Hello, Tom!' 

If you don't want the currying/partial application behaviour, there is an immediate application version named (appropriately), callNow (and also another named applyNow, not shown):

callNow(greet, 'Hello', 'Tom')
  //=> 'Hello, Tom!' 
  
callNow(greet, 'Hello')
  //=> 'Hello, undefined!' 

callRight applies any arguments supplied to the right. If you supply all the arguments, it's the same as call, but if you supply fewer arguments, you get a right partial application:

callRight(greet, 'Hello', 'Tom')
  //=> 'Hello, Tom!' 
  
callRight(greet, 'Hello')('Tom')
  //=> 'Tom, Hello!' 

callFlipped applies the arguments backwards, even when curried:

callFlipped(greet, 'Hello', 'Tom')
  //=> 'Tom, Hello!' 
  
callFlipped(greet, 'Hello')('Tom')
  //=> 'Tom, Hello!' 
  
callFlipped(greet)('Hello')('Tom')
  //=> 'Tom, Hello!' 

callLeft is actually synonymous with call: It applies arguments given to the left. We've seen callRight above. Both are variadic: You can supply as many arguments as you want.

callFirst and callLast are just like callLeft and callRight, but they are binary functions: They accept a function and exactly one argument. This is sometimes useful when combining functions together.

callFirst and callLast both have "flipped and curried" versions (callFirstWith and callLastWith). callLastWith is especially useful for working with functions written in "collection - operation" style. Here we take advantage of the fact that they are "automatically curried" to implement the popular pluck function.

allong.es does support the curry function, it is implemented as the unary form of call:

var curry = unary(call);

splat was present in earlier versions of allong.es but has been deprecated as being too cryptic. Instead, there is a general naming convention that works as follows. Many binary functions such as map and filter are historically written to take a noun or collection as the first argument and a verb as the second.

However, reversing and currying these functions is super-useful as it makes composeable functions out of them. That's why callFlipped is so important. But to save you the trouble of writing callFlipped map everywhere, many such functions in allong.es have a clipped version pre-defined and named with the suffix With:

map(list, function)       <=> mapWith(function, list)
filter(list, function)    <=> filterWith(function, list)
get(object, propertyName) <=> getWith(propertyName, object)
pluck(list, propertyName) <=> pluckWith(propertyName, list)

So you "map" a list, but "mapWith" a function. And of course, they are all curried. For example:

map(list)(function)       <=> mapWith(function)(list)
deepMap(list)(function)   <=> deepMapWith(function)(list)
filter(list)(function)    <=> filterWith(function)(list)
get(object)(propertyName) <=> getWith(propertyName)(object)
pluck(list)(propertyName) <=> pluckWith(propertyName)(list)

Thus if you have a collection such as:

var users = [
  { name: 'Huey' },
  { name: 'Dewey' },
  { name: 'Louie' }
]

You can get the names with either:

pluck(users, 'name')
  //=> ['Huey', 'Dewey', 'Louie'] 

Or:

pluckWith('name', users)
  //=> ['Huey', 'Dewey', 'Louie'] 

The latter is interesting because pluck and pluckWith are both automatically curried (like almost everything that isn't named "now"). Thus, we could also write:

var namesOf = pluckWith('name');
 
// ... 
namesOf(users)
  //=> ['Huey', 'Dewey', 'Louie'] 

Makes a function into a variadic (accepts any number of arguments). The last named parameter will be given an array of arguments.

var variadic = require('allong.es').allong.es.variadic;
 
var fn = variadic(function (a) { return a })
 
fn()
  //=> [] 
fn(1, 2, 3)
  //=> [1,2,3] 
 
fn = variadic(function (a,b) { return { a: a, b: b } })
 
fn()
  //=> { a: undefined, b: [] } 
fn(1)
  //=> { a: 1, b: [] } 
fn(1,2,3)
  //=> { a: 1, b: [2, 3] } 

When given just the function, variadic returns a function with an arity of zero. This is consistent with JavaScript programming practice. There are times when you wish to report an arity, meaning that you want the returned function to have its length getibute set.

You do this by prefacing the function argument with a length:

fn = variadic(function (a,b) { return { a: a, b: b } });
 
fn.length
  //=> 0 
  
fn2 = variadic(1, function (a,b) { return { a: a, b: b } }); 
 
fn2.length
  //=> 1 

Sometimes, you have a function that takes multiple arguments, but you only want it to accept one, or two, or maybe three arguments and ignore the rest. For example, parseInt takes a radix as an optional second parameter. And that is havoc if you try to use it with Array.map:

['1', '2', '3', '4', '5'].map(parseInt)
  //=> [ 1, 
  //     NaN, 
  //     NaN, 
  //     NaN, 
  //     NaN ] 

Use unary(parseInt) to solve the problem:

['1', '2', '3', '4', '5'].map(unary(parseInt))
  //=> [ 1, 2, 3, 4, 5 ] 

binary has similar uses when working with Array.reduce and its habit of passing three parameters to your supplied function.

var bound = require('allong.es').allong.es.bound;
    
bound(fn, args...)(obj)
  //=> fn.bind(obj, args...) 
var getWith = require('allong.es').allong.es.getWith;
    
array.map(getWith('property'))
  //=> array.map(function (element) { 
  //               return element['property'] 
  //             }) 
var compose = require('allong.es').allong.es.compose,
    sequence = require('allong.es').allong.es.sequence;
    
compose(a, b, c)
  //=> function (x) { 
  //     return a(b(c(x))) 
  //   } 
 
sequence(a, b, c)
  //=> function (x) { 
  //     return c(b(a(x))) 
  //   } 
var mapWith = require('allong.es').allong.es.mapWith,
    deepMapWith = require('allong.es').allong.es.deepMapWith;
    
var squareList = mapWith(function (x) { return x * x })
 
squareList([1, 2, 3, 4])
  //=> [1, 4, 9, 16] 
  
var squareTree = deepMapWith(function (x) { return x * x })
 
squareTree([1, 2, [3, 4]])
  //=> [1, 4, [9, 16]] 
var maybe = require('allong.es').allong.es.maybe;
    
var safeFirst = maybe(function (arr) { return arr[0] })
 
safeFirst([1, 2, 3])
  //=> 1 
safeFirst(null)
  //=> null 
var tap = require('allong.es').allong.es.tap;
    
tap([1, 2, 3, 4, 5], send('pop'))
  //=> [1, 2, 3, 4] 
var fluent = require('allong.es').allong.es.fluent;
    
Role = function () {}
 
Role.prototype.set = fluent( function (propertyname) { 
  this[property] = name 
})
 
var doomed = new Role()
  .set('name', "Fredo")
  .set('relationship', 'brother')
  .set('parts', ['I', 'II'])
var once = require('allong.es').allong.es.once;
    
var message = once( function () { console.log("Hello, it's me") })
 
message()
  //=> "Hello, it's me" 
message()
  //=> 
message()
  //=> 
message()
  //=> 
var mixin = require('allong.es').allong.es.mixin,
    classDecorator = require('allong.es').allong.es.classDecorator;
    
function Todo (name) {
  var self = this instanceof Todo
             ? this
             : new Todo();
  self.name = name || 'Untitled';
  self.done = false;
};
 
Todo.prototype.do = fluent( function () {
  this.done = true;
});
 
Todo.prototype.undo = fluent( function () {
  this.done = false;
});
 
var AddLocation = mixin({
      setLocation: fluent( function (location) {
        this.location = location;
      }),
      getLocationfunction () { return this.location; }
    });
 
AddLocation.call(Todo.prototype);
// Or use AddLocation(Todo.prototype) 
 
new Todo("Vacuum").setLocation('Home');
  //=> { name: 'Vacuum', 
  //     done: false, 
  //     location: 'Home' } 
 
var AndColourCoded = classDecorator({
  setColourRGB: fluent( function (rgb) {
    this.colourCode = { r: r, g: g, b: b };
  }),
  getColourRGBfunction () {
    return this.colourCode;
  }
});
 
var ColourTodo = AndColourCoded(Todo);
 
new ColourTodo('Use More Decorators').setColourRGB(0, 255, 0);
  //=> { name: 'Use More Decorators', 
  //     done: false, 
  //     colourCode: { r: 0, g: 255, b: 0 } } 

Note: classDecorator works with JavaScript constructors that have a default implementation (they work properly with no arguments), and are new-agnostic (they can be called with new or as a normal function). Todo above has both properties.

Functional iterators are stateful functions that "iterate over" the values in some ordered data set. You call the iterator repeatedly to obtain the values, and it will either never stop returning values (an infinite data set) or return undefined when there are no more values to return.

The functional iterators utilities are all namespaced:

var iterators = require('allong.es').allong.es.iterators;

Making functional iterators from arrays:

var FlatArrayIterator = iterators.FlatArrayIterator,
    RecursiveArrayIterator = iterators.RecursiveArrayIterator;
    
var i = FlatArrayIterator([1, 2, 3, 4, 5]);
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
i();
  //=> 4 
i();
  //=> 5 
i();
  //=> undefined 
    
var i = FlatArrayIterator([1, [2, 3, 4], 5]);
 
i();
  //=> 1 
i();
  //=> [2, 3, 4] 
i();
  //=> 5 
i();
  //=> undefined 
    
var i = RecursiveArrayIterator([1, [2, 3, 4], 5]);
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
i();
  //=> 4 
i();
  //=> 5 
i();
  //=> undefined 
var range = iterators.range,
    numbers = iterators.numbers;
 
var i = range(1, 5);
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
i();
  //=> 4 
i();
  //=> 5 
i();
  //=> undefined 
 
var i = range(1, 5, 2);
 
i();
  //=> 1 
i();
  //=> 3 
i();
  //=> 5 
i();
  //=> undefined 
 
var i = range(5, 1);
 
i();
  //=> 5 
i();
  //=> 4 
i();
  //=> 3 
i();
  //=> 2 
i();
  //=> 1 
i();
  //=> undefined 
 
var i = range(1);
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
// ... 
 
var i = numbers();
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
// ... 
 
var i = numbers(0);
 
i();
  //=> 0 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
// ... 

Unfold makes an iterator out of a seed by successively applying a function to the seed value. Here's an example duplicating the "numbers" feature:

var unfold = iterators.unfold,
    unfoldWithReturn = iterators.unfoldWithReturn;
    
var i = unfold(1, function (n) { return n + 1; });
 
i();
  //=> 1 
i();
  //=> 2 
i();
  //=> 3 
// ... 
    
var i = unfoldWithReturn(1, function (n) { 
  return [+ 1, n + n]; 
});
 
i();
  //=> 2 
i();
  //=> 4 
i();
  //=> 6 
// ... 

A richer example of unfoldWithReturn:

var cards = ['A', 2, 3, 4, 5, 6, 7, 8, 9, '10', 'J', 'Q', 'K'];
 
function pickCard (deck) {
  var position;
  
  if (deck.length === 0) {
    return [[], void 0];
  }
  else {
    position = Math.floor(Math.random() * deck.length);
    return [
      deck.slice(0, position).concat(deck.slice(position + 1)),
      deck[position]
    ];
  }
};
 
var i = unfoldWithReturn(cards, pickCard);
 
i();
  //=> 5
i();
  //=> 4
i();
  //=> 2
i();
  //=> J
  
// ...

Stateless mapping of an iterator to another iterator:

var map = iterators.map;
    
var squares = map(numbers, function (n) { return n * n; });
 
squares();
  //=> 1 
squares();
  //=> 4 
squares();
  //=> 9 
// ... 

Accumulating an iterator to another iterator, a/k/a stateful mapping, with an optional seed:

var accumulate = iterators.accumulate;
    
var runningTotal = accumulate(numbers, function (accumulationn) { 
      return accumulation + n; 
    });
 
runningTotal();
  //=> 1 
runningTotal();
  //=> 3 
runningTotal();
  //=> 6 
runningTotal();
  //=> 10 
runningTotal();
  //=> 15 
// ... 
 
var runningTotal = accumulate(numbers, function (accumulationn) { 
      return accumulation + n; 
    }, 5);
 
runningTotal();
  //=> 6 
runningTotal();
  //=> 8 
runningTotal();
  //=> 11 
runningTotal();
  //=> 15 
runningTotal();
  //=> 20 
// ... 

This code transforms filters duplicates out of an iterator of numbers by turning them into "false." It consumes space proportional to the time it runs and the size of the set of possible numbers in its iterator.

var accumulateWithReturn = iterators.accumulateWithReturn;
    
var randomNumbers = function () {
  return Math.floor(Math.random() * 10);
};
 
randomNumbers();
  //=> 7 
randomNumbers();
  //=> 0 
randomNumbers();
  //=> 1 
randomNumbers();
  //=> 1 
randomNumbers();
  //=> 6 
// ... 
 
var uniques = accumulateWithReturn(randomNumbers, function (alreadySeennumber) {
  var key = number.toString();
  
  if (alreadySeen[key]) {
    return [alreadySeen, false];
  }
  else {
    alreadySeen[key] = true;
    return [alreadySeen, number];
  }
}, {});
 
uniques();
  //=> 7 
uniques();
  //=> 5 
uniques();
  //=> 1 
uniques();
  //=> false 
uniques();
  //=> 9 
uniques();
  //=> 4 
uniques();
  //=> false 
// ... 
var select = iterators.select,
    reject = iterators.reject;
 
function isEven (number) {
  return number === 0 || !isEven(number - 1);
};
 
var evens = select(randomNumbers, isEven);
 
evens();
  //=> 0 
evens();
  //=> 6 
evens();
  //=> 0 
evens();
  //=> 2 
evens();
  //=> 4 
// ... 
 
var odds = reject(randomNumbers, isEven);
 
odds();
  //=> 3 
odds();
  //=> 1 
odds();
  //=> 7 
odds();
  //=> 9 
odds();
  //=> 9 
// ... 

Note: select and reject will enter an "infinite loop" if the iterator does not terminate and also does not have any elements matching the condition.

var slice = iterators.slice,
    numbers = unfold(1, function (n) { return n + 1; });
 
var i = slice(numbers, 3);
 
i();
  //=> 4 
i();
  //=> 5 
i();
  //=> 6 
 
= slice(numbers, 3, 2);
 
i();
  //=> 10 
i();
  //=> 11 
i();
  //=> undefined 
var take = iterators.take,
    numbers = unfold(1, function (n) { return n + 1; });
 
var i = take(numbers);
 
i();
  //=> 1 
i();
  //=> undefined 
 
var i = take(numbers);
 
i();
  //=> 2 
i();
  //=> undefined 
 
var i = take(numbers, 3);
 
i();
  //=> 3 
i();
  //=> 4 
i();
  //=> 5 
i();
  //=> undefined 
// ... 
var drop = iterators.drop,
    numbers = unfold(1, function (n) { return n + 1; });
 
drop(numbers);
 
numbers();
  //=> 2 
numbers();
  //=> 3 
numbers();
  //=> 4 
 
drop(numbers);
 
numbers();
  //=> 6 
numbers();
  //=> 7 
 
drop(numbers, 3);
 
numbers();
  //=> 11 
numbers();
  //=> 12 
// ... 
var trampoline = require('allong.es').allong.es.trampoline,
    tailCall = require('allong.es').allong.es.tailCall;
    
function factorial (n) {
  var _factorial = trampoline( function myself (acc, n) {
    return n > 0
      ? tailCall(myself, acc * n, n - 1)
      : acc
  });
  
  return _factorial(1, n);
};
 
factorial(10);
  //=> 3628800