Last active
July 9, 2020 07:12
-
-
Save casperin/cbe64e5ec803859158ae to your computer and use it in GitHub Desktop.
Some examples of currying and composing functions in javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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