Skip to content

Instantly share code, notes, and snippets.

@YozhEzhi
Last active March 1, 2017 09:03
Show Gist options
  • Save YozhEzhi/876c4d05ba3895cd0a6d4db8ea248f39 to your computer and use it in GitHub Desktop.
Save YozhEzhi/876c4d05ba3895cd0a6d4db8ea248f39 to your computer and use it in GitHub Desktop.
/**
* @author https://github.com/vasanthk/js-bits/blob/master/js/currying.js
* Currying refers to the process of transforming a function with multiple arity (# or args a fn accepts)
* into the same function with less arity.
*
* Briefly, currying is a way of constructing functions that allows partial application of a function’s arguments.
* What this means is that you can pass all of the arguments a function is expecting and get the result,
* or pass a subset of those arguments and get a function back that’s waiting for the rest of the arguments.
*
* Currying vs Partial Application
* “Currying is the decomposition of a polyadic function into a chain of nested unary functions.
* Thus decomposed, you can partially apply one or more arguments, although the curry operation itself does not
* apply any arguments to the function.”
*/
// Simple Greet function -- Non Curried
const greet = function (greeting, name) {
console.log(`${greeting}, ${name}`);
};
greet('Hello', 'Vasa'); // 'Hello, Vasa'
// Curried version
const greetCurried = function (greeting) {
return function (name) {
console.log(`${greeting}, ${name}`);
}
};
// This tiny adjustment lets us create a new function for any type of greeting,
// and pass that new function the name of the person that we want to greet:
const greetHello = greetCurried('Hello');
greetHello('Vasa'); // 'Hello, Vasa'
greetHello('Vignesh'); // 'Hello, Vignesh'
// We can also call the original curried function directly by passing each of the params in a separate set of parentheses:
greetCurried('Hi there')('Vasa'); // 'Hi there, Vasa'
// Currying traditional functions -- SIMPLE VERSION
// The only problem with the currying approach is the syntax. As you build these curried functions up, you need to keep
// nesting returned functions, and call them with new functions that require multiple sets of parentheses,
// each containing its own isolated argument. It can get messy.
// To address that problem, one approach is to create a quick and dirty currying function that will take the name of an
// existing function that was written without all the nested returns.
// A currying function would need to pull out the list of arguments for that function, and use those to return a curried
// version of the original function:
// Partial Application -- Few arguments passed initially + allowing more args to be passed later on.
function curryIt(uncurriedFn) {
// Omit 0th argument (which is the uncurriedFn and start from index 1)
var parameters = Array.prototype.slice.call(arguments, 1);
return function () {
return uncurriedFn.apply(this, parameters.concat(
Array.prototype.slice.call(arguments, 0)
));
};
}
var greeter = function (greeting, separator, emphasis, name) {
console.log(greeting + separator + name + emphasis);
};
var greetHello = curryIt(greeter, 'Hello', ', ', '.');
greetHello('Heidi'); // 'Hello, Heidi.'
greetHello('Eddie'); // 'Hello, Eddie.'
// curryIt --- BETTER (?) VERSION
// Reference: https://medium.com/@kevincennis/currying-in-javascript-c66080543528#.bnk4cy1m0
function curryIt(fn) {
var arity = fn.length;
return (function resolver() {
var memory = Array.prototype.slice.call(arguments);
return function () {
var local = memory.slice(), next;
Array.prototype.push.apply(local, arguments);
next = local.length >= arity ? fn : resolver;
return next.apply(null, local);
};
}());
}
var l = 2, b = 3, h = 4;
var curriedVol = curryIt(vol);
var area = curriedVol(l)(b);
var volume = area(h);
console.log('Volume: ', volume);
function vol(l, b, h) {
return l * b * h;
}
// ES6 Example
const one = document.getElementById('one');
const two = document.getElementById('two');
const three = document.getElementById('three');
const f = a => b => c => a.addEventListener(b, (event) => event.target.style.color = c);
const oneEventColor = f(one);
const twoEventColor = f(two);
oneEventColor('mouseover')('blue');
twoEventColor('mouseout')('green');
// Sum numbers example:
function add(...args) {
return (args.length > 0) ? args.reduce((a, b) => a + b) : 0;
}
function curry(func) {
return function(...nextArgs) {
// run function
if (nextArgs.length === 0) return func.apply(this, [...nextArgs]);
// else return a partially applied function
return curry(func.bind.apply(func, [this, ...nextArgs]));
};
}
const sum = curry(add);
console.log(sum(), 0);
console.log(sum(3)(2)(5)(8)(3)(), 21);
const sum10 = sum(2)(4)(4); // ==> Function
console.log(sum10(), 10); // ==> 10
const sum13 = sum10(3); // ==> Function
console.log(sum13(), 13); // ==> 13
console.log(sum13(-4)(), 9); // ==> 9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment