Instantly share code, notes, and snippets.

Embed
What would you like to do?
Functional programming toolkit for javascript
//this function composes functions like in Haskell (and most other functional languages)
//the final composed function can accept parameters dictated by the chain it creates
//you can pass it a list of functions and it shall apply them from RIGHT to LEFT (right associativeness?)
//eg., c0(fn1, fn2, fn3)(10) => return fn1(fn2(fn3(10)));
// out<---<----<----<=10
function c0(f, g) {
var last;
if (!arguments.length) return;
if (arguments.length === 1) return f;
if (arguments.length === 2) {
return function (x) {
var args = [].slice.call(arguments);
args.shift();
args.push(g(x));
return f.apply(this, args);
};
}
last = [].pop.call(arguments);
return c0.apply(null, ([].push.call(arguments, c0([].pop.call(arguments), last)), arguments));
}
//simple example:
function add3(x) {
return x + 3;
}
function mult10(x) {
return x * 10;
}
function negate(x) {
return -x;
}
chain = c0(negate, mult10, add3);
//chain is now a reusable function composed of 3 other functions
//let's use it then
chain(10); //negate(mult10(add3(10))) -> -130
chain(20); //-230
//c0 and cu (curry.js, below) are meant to be used together to get powerful function chains
//this function returns a function that curries itself if it doesn't have enough parameters
//when it has enough parameters, it runs the function and gives you the result
function cu(fn) {
var args = [].slice.call(arguments);
if (args.length - 1 >= fn.length) return fn.apply(this, args.slice(1));
return function () {
var tempArgs = args.concat([].slice.call(arguments));
return cu.apply(this, tempArgs);
}
}
//simple example:
//add a,b and c
function add(a, b, c) {
return a + b + c;
}
var add = cu(add);
//curry it with a = 1
var add1 = add(1);
//curry it so that a=1 and b=2
var add1plus2 = add1(2);
console.log(add(1, 2, 5) === add1(2, 5)); //true
console.log(add(1, 2, 5) === add1plus2(5)); //true
//function composition goodness
function c0(f, g) {
var last;
if (!arguments.length) return;
if (arguments.length === 1) return f;
if (arguments.length === 2) {
return function (x) {
var args = [].slice.call(arguments);
args.shift();
args.push(g(x));
return f.apply(this, args);
};
}
last = [].pop.call(arguments);
return c0.apply(null, ([].push.call(arguments, c0([].pop.call(arguments), last)), arguments));
}
//automagical self-currying function goodness
function cu(fn) {
var args = [].slice.call(arguments);
if (args.length - 1 >= fn.length) return fn.apply(this, args.slice(1));
return function () {
var tempArgs = args.concat([].slice.call(arguments));
return cu.apply(this, tempArgs);
}
}
/********************************************************************
* Example 1: Single argument to composed function
* -----------------------------------------------
*********************************************************************/
//filters out a particular item (given by 'x') from the array
function filterOut(x, arr) {
return arr.filter(function (v) {
if (x !== v) return v;
})
}
//multiples each item in the array with the value 'x'
function multiplyWith(x, arr) {
return arr.map(function (v) {
return x * v;
});
}
//make a self-currying version of [].reduce
reduce = cu([].reduce);
//display the array and then sum up all its items and return it
function showAndReduce(arr) {
console.log(arr);
return reduce.call(arr, function (p, c) {
return p + c
}, 0);
}
//test input
var someArray = [100, 1, 2, 100, 100, 3, 100, 4, 100];
//create a chain that filters out all 100's,
//multiplies each item with 10 and then shows and returns the sum
var chainOne = c0(showAndReduce, cu(multiplyWith, 10), cu(filterOut, 100));
chainOne(someArray);
//output:
//[10, 20, 30, 40]
//100
chainOne([100, 100, 100, 100, 100, 1, 1, 1, 1, 2, 100]);
//output:
//[10, 10, 10, 10, 20]
//60
/********************************************************************
* Example 2: multiple args to a composed function
* -----------------------------------------------
*********************************************************************/
//We shall be using map, filter and with a few helpers
//map, filter, add & lessThan all auto-curry. Flip need not auto-curry but its result does
var map = cu(function map(list, fn) {
try {
return list.map(fn);
}
catch (e) {
return [].map.call(list, fn);
}
});
var filter = cu(function filter(list, fn) {
try {
return list.filter(fn);
}
catch (e) {
return [].filter.call(list, fn);
}
});
var add = cu(function(a, b){ return a+b; });
var lessThan = cu(function(a,b){ return b<a; }); //the function reads "lessThan 'a' is 'b'?" or "is 'b' lessThan 'a'"
var flip = function(fn){
return cu(function(a,b){
return fn.call(this, b, a);
});
};
//add 1 to every element in the array and return the result
c0(map, add)(1, [1,2,3,4]); //[2, 3, 4, 5]
//give me every element that is lesser than 4 in the array
c0(filter, lessThan)(4, [0,1,2,3,4]); //[0, 1, 2, 3]
//string fun
c0(map, add)(" ola", "abcd"); //[" olaa", " olab", " olac", " olad"]
//but wait, that's not what we wanted. We wanted ["a ola", "b ola", ...]
c0(map, flip(add))(" ola", "abcd"); //["a ola", "b ola", "c ola", "d ola"]
var map = cu(function map(list, fn) {
try {
return list.map(fn);
}
catch (e) {
return [].map.call(list, fn);
}
});
var filter = cu(function filter(list, fn) {
try {
return list.filter(fn);
}
catch (e) {
return [].filter.call(list, fn);
}
});
//accepts function that take two parameter and flips its params
var flip = function(fn){
return cu(function(a,b){
return fn.call(this, b, a);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment