• Stars
    star
    486
  • Rank 90,003 (Top 2 %)
  • Language
    JavaScript
  • Created over 11 years ago
  • Updated almost 9 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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.

Currying and Partial Application

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 (how, whom) {
  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!'

immediate application

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!'

variations on the order of applying the arguments

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!'

more partial application

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.

currying

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

var curry = unary(call);

with

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']

Arity Function Decorators

variadic

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] }

variadic, part ii

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

unary, binary, and ternary

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.

Miscellaneous Combinators

bound

var bound = require('allong.es').allong.es.bound;
    
bound(fn, args...)(obj)
  //=> fn.bind(obj, args...)

getWith

var getWith = require('allong.es').allong.es.getWith;
    
array.map(getWith('property'))
  //=> array.map(function (element) {
  //               return element['property']
  //             })

Functional Composition

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)))
  //   }

List Combinators

mapWith and deepMapWith

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]]

Function/Method Decorators

maybe

var maybe = require('allong.es').allong.es.maybe;
    
var safeFirst = maybe(function (arr) { return arr[0] })

safeFirst([1, 2, 3])
  //=> 1
safeFirst(null)
  //=> null

tap

var tap = require('allong.es').allong.es.tap;
    
tap([1, 2, 3, 4, 5], send('pop'))
  //=> [1, 2, 3, 4]

fluent

var fluent = require('allong.es').allong.es.fluent;
    
Role = function () {}

Role.prototype.set = fluent( function (property, name) { 
  this[property] = name 
})

var doomed = new Role()
  .set('name', "Fredo")
  .set('relationship', 'brother')
  .set('parts', ['I', 'II'])

once

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()
  //=>

Decorating Classes/Constructors

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;
      }),
      getLocation: function () { 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 (r, g, b) {
    this.colourCode = { r: r, g: g, b: b };
  }),
  getColourRGB: function () {
    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

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;

FlatArrayIterator and RecursiveArrayIterator

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

range and numbers

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 and unfoldWithReturn

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 [n + 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
  
// ...

map

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
// ...

accumulate

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

var accumulate = iterators.accumulate;
    
var runningTotal = accumulate(numbers, function (accumulation, n) { 
      return accumulation + n; 
    });

runningTotal();
  //=> 1
runningTotal();
  //=> 3
runningTotal();
  //=> 6
runningTotal();
  //=> 10
runningTotal();
  //=> 15
// ...

var runningTotal = accumulate(numbers, function (accumulation, n) { 
      return accumulation + n; 
    }, 5);

runningTotal();
  //=> 6
runningTotal();
  //=> 8
runningTotal();
  //=> 11
runningTotal();
  //=> 15
runningTotal();
  //=> 20
// ...

accumulateWithReturn

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 (alreadySeen, number) {
  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
// ...

select and reject

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.

slice

var slice = iterators.slice,
    numbers = unfold(1, function (n) { return n + 1; });

var i = slice(numbers, 3);

i();
  //=> 4
i();
  //=> 5
i();
  //=> 6

i = slice(numbers, 3, 2);

i();
  //=> 10
i();
  //=> 11
i();
  //=> undefined

take

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
// ...

drop

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
// ...

Trampolining

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

More Repositories

1

javascript-allonge

Markdown source for the book "JavaScript Allongé"
JavaScript
758
star
2

presentations

Conference Talks and Proposals
751
star
3

andand

The Maybe Monad in idiomatic Ruby
Ruby
302
star
4

JQuery-Combinators

The jQuery plugin with the academic name and the pragmatic methods
JavaScript
213
star
5

YouAreDaChef

Coffeescript/Javascript method combinations for Underscore projects
CoffeeScript
183
star
6

Katy

CoffeeScript and JavaScript Combinators
CoffeeScript
172
star
7

method-combinators

CoffeeScript
120
star
8

javascript-allonge-six

https://leanpub.com/javascriptallongesix
JavaScript
104
star
9

hashlife

JavaScript
58
star
10

oscin.es

n. pl. 1. (Zool.) Singing birds; a group of the Passeres, having numerous syringeal muscles, conferring musical ability
JavaScript
55
star
11

javascript-spessore

Manuscript for JavaScript Spessore
JavaScript
49
star
12

Underscore-Matchers-for-Jasmine

CoffeeScript
39
star
13

raganwald.github.com

raganwald.com jekyll source
HTML
31
star
14

cafeaulife

Gosper’s HashLife in CoffeeScript
CoffeeScript
30
star
15

wood_and_stones

Use your iPad as a Goban
JavaScript
29
star
16

combinators.info

JavaScript
23
star
17

jQuery-Predicates

.exists() and does_not_exist() for jQuery
JavaScript
16
star
18

ristrettolo.gy

jekyll source for http://ristrettolo.gy
JavaScript
10
star
19

FRACTRAN

Code to accompany “ Remembering John Conway's FRACTRAN, a ridiculous, yet surprisingly deep language”
JavaScript
10
star
20

supervis.es

CoffeeScript
9
star
21

string-lambdas

String lambdas for JavaScript and CoffeeScript
CoffeeScript
8
star
22

braythwayt.com

Reg Braithwaite's old weblog plus the odd non-technical blow hardiness
HTML
7
star
23

the-little-elixir-book

Fun with "The Little Elixir and OTP Guidebook"
Elixir
6
star
24

presentation_decks

Presentation decks from a few speaking engagements
6
star
25

raganwald.posterous.com

The content from my posterous blog, somewhat
5
star
26

mazerunner

Builds a maze in ES6 using Eller's Algorithm as described by Jamis Buck.
JavaScript
5
star
27

AMA

A lightweight “Ask me anything” repository inspired by @holman and @r00k
5
star
28

still-failing-still-learning-still-optimistic

3
star
29

stata

Read and write support for the Stata binary format
C
2
star
30

MacOSMendel

Java
1
star
31

shunting-yard

The collected shunting-yard code from raganwald.com
JavaScript
1
star
32

shunting-yard-dot-rb

A toy shunting yard implementation in Ruby
Ruby
1
star
33

kato

CoffeeScript-style OO for JavaScript
1
star