Skip to content

Instantly share code, notes, and snippets.

@casperin
Last active July 9, 2020 07:12
Show Gist options
  • Save casperin/cbe64e5ec803859158ae to your computer and use it in GitHub Desktop.
Save casperin/cbe64e5ec803859158ae to your computer and use it in GitHub Desktop.
Some examples of currying and composing functions in javascript
var log = console.log.bind(console);
// Core component (not curried).
// compose(g, f) === function (x) { return g(f(x)); }
// Takes any number of functions as arguments
// from underscore.js
function compose (/* fn1, fn2, ... */) {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
}
// Curried core functions (There are plenty more)
var map = autoCurry(function (fn, arr) { return arr.map(fn); }),
filter = autoCurry(function (fn, arr) { return arr.filter(fn); }),
reduce = autoCurry(function (fn, initial, arr) { return arr.reduce(fn, initial); }),
add = autoCurry(function (x, y) { return y + x; }),
subtract = autoCurry(function (x, y) { return y - x; }),
multiply = autoCurry(function (x, y) { return y * x; }),
divide = autoCurry(function (x, y) { return y / x; }),
mod = autoCurry(function (x, y) { return y % x; }),
eq = autoCurry(function (x, y) { return x === y; }),
neq = autoCurry(function (x, y) { return !eq(x, y); }),
lt = autoCurry(function (x, y) { return y < x; }),
gt = autoCurry(function (x, y) { return y > x; }),
lteq = autoCurry(function (x, y) { return y <= x; }),
gteq = autoCurry(function (x, y) { return y >= x; }),
even = function (x) { return x % 2 === 0; },
odd = function (x) { return !even(x); },
get = autoCurry(function (prop, obj) { return obj[prop]; }),
// Data
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
people = [
{name: "Alice", age: 10},
{name: "Bob", age: 61},
{name: "Carol", age: 32},
{name: "Donna", age: 25}
];
/**
* Examples
*/
log("Normal mapping",
numbers.map(function (x) { return x + 1; })
);
// With arrow functions (ES6) it can be written as
// a.map((x) => x + 1);
// `add` is curried
var addOne = add(1);
log("Using currying of `add`",
numbers.map(addOne)
);
// ... so is `map`
// A function that adds one to every item in an array
var mapAddOne = map(add(1));
// or:
// mapAddOne = map(addOne);
log("Using currying for both `map` and `add`",
mapAddOne(numbers)
);
// Same, but only for to even numbers.
// This function will FIRST filter for even numbers, THEN add one
var getEvenNumbersAndAddOne = compose(mapAddOne, filter(even));
log("Filter even numbers, then add one",
getEvenNumbersAndAddOne(numbers)
);
// This is why sub (and gt and lt) has "reversed" parameters
log("Subtract 5 from everything in array",
map(subtract(5), numbers)
);
// Very common pattern
var getNames = map(get("name"));
log("Getting all names",
getNames(people)
);
// Lets get names of people with even ages
var getEvenAges = filter(compose(even, get("age"))),
getNamesWithEvenAges = compose(getNames, getEvenAges);
log("Getting people with even ages",
getNamesWithEvenAges(people)
);
// Get names of old people
var getOldPeople = filter(compose(gt(30), get("age"))),
getNamesOfOldPeople = compose(getNames, getOldPeople);
log("Getting names of old people",
getNamesOfOldPeople(people)
);
// Just for clarity:
log("The entire `person` object of old people",
getOldPeople(people)
);
// The magic. No need to go through the details of these functions.
function curry(fn) {
var toArray = function(arr, from) {
return Array.prototype.slice.call(arr, from || 0);
},
args = toArray(arguments, 1);
return function() {
return fn.apply(this, args.concat(toArray(arguments)));
};
}
function autoCurry(fn, numArgs) {
var toArray = function(arr, from) {
return Array.prototype.slice.call(arr, from || 0);
};
numArgs = numArgs || fn.length;
return function() {
var rem;
if (arguments.length < numArgs) {
rem = numArgs - arguments.length;
if (numArgs - rem > 0) {
return autoCurry(curry.apply(this, [fn].concat(toArray(arguments))), rem);
} else {
return curry.apply(this, [fn].concat(toArray(arguments)));
}
} else {
return fn.apply(this, arguments);
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment