Skip to content

Instantly share code, notes, and snippets.

@andrewb
Created October 30, 2017 21:04
Show Gist options
  • Save andrewb/50e8522d6c2c5ef9cbee37b7ecc4656b to your computer and use it in GitHub Desktop.
Save andrewb/50e8522d6c2c5ef9cbee37b7ecc4656b to your computer and use it in GitHub Desktop.
import curry from 'lodash.curry';
/*
CURRYING
What is a curried function?
It's a function that takes multiple parameters,
one at a time.
What does that mean?
Well, imagine a function that has three parameters,
like:
function foo(a, b, c) {
...
}
The curried version will take one argument and return a
function that takes the next argument. This returned function
will also return a function that takes the third and final
argument. Calling this function will return the result of the
function.
What?
foo(1, 2, 3); // a flavorless function
foo(1)(2)(3); // a tasy, curried function
Let's make this a little more concrete. Image we want a curried
function that adds two numbers together. How could we implement
it in a naive way?
const addAB = (a) => {
return (b) => {
return a + b;
};
}
Or, more simply...
const addAB = a => b => a + b;
*/
const addAB = a => b => a + b;
console.log( addAB(1)(2) ); // 3
const add10 = addAB(10);
console.log( add10(10) ); // 20
/*
The above example is contrived and impractical. We'll look at
real life applications of currying soon. For now, the main
thing is that the concept makes sense. Before I go on is it
clear to everyone?
PARTIAL APPLICATION
We know that a curried function will return a function that takes
exactly one argument.
Partial application is similar, but it will return a function that
takes the remaining arguments.
Huh?
To demonstrate this lets imagine we have a new function that adds
A, B and C.
If we curried this function we would use it like this:
addABC(1)(2)(3); // 6
But, if we partially applied the function we could use it like this...
*/
const addABC = (a, b, c) => {
return a + b + c;
}
// To keep it simple, let's just use bind for this demonstration...
const addBC = addABC.bind(null, 1);
// Our new function already has A applied
console.log( addBC(2, 3) ); // 6
const addC = addABC.bind(null, 1, 2);
// Our new function already has A and B applied
console.log( addC(3) ); // 6
/*
The distinction is important. A curried function will always produce
unary functions. The same is not always true of partial application.
As we saw above it can return unary functions (addC), but it return
functions that take multiple arguments too.
The partial application example above is a little less tedious than
our curried example. But IRL we would want something more flexible. You
could write your own functions to implement curring and partial application
or you could use something like lodash.
A simple implementation would be:
(binary)
const curry2 = f => x => y => f(x, y); // curry2(addAB)
(ternary)
const curry3 = f => x => y => z => f(x, y, z); // curry3(addABC)
Or, for more flexibility use lodash. Note, the lodash curry function
is not true currying. It is best described as auto-currying. It
basically lets us curry, or partially apply in any order.
*/
// Lodash (auto)-currying
const curried = curry(addABC);
console.log( curried(1, 2, 3) ); // 6 (call it)
console.log( curried(1, 2)(3) ); // 6 (partial)
console.log( curried(1)(2, 3) ); // 6 (partial)
console.log( curried(1)(2)(3) ); // 6 (curry)
/*
Great, so we can curry or partially apply any function easily using lodash
or another lib, but when why would I want to do it?
Getting a little less abstract curry works well with functional composition
because it returns unary functions.
*/
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const square = x => x * x;
// (25 + 10) * 10
console.log( pipe(square, curry(add)(10), curry(multiply)(10) )(5) ); // 350
/*
Ok, that's still pretty abstract, but if you want to do functional
programming you'll use it frequently.
Even if you're not doing functional programming currying can still be useful.
Imagine this code...
*/
const sanitize = (blacklist, string) => {
// imagine this returns a new string with all
// of the blacklisted values removed.
// e.g. sanitize(['foo', 'bar'], 'foo bar baz')
// returns 'baz'
}
const strings = [
'My great string',
'Another great string'
];
// Without curry
const blacklist = ['foo', 'bar'];
const sanitized1 = strings.map((str) => sanitize(blacklist, str));
// Or, with...
const clean = curry(sanitize)(['foo', 'bar']); // returns unary function
const sanitized2 = strings.map(clean);
/*
Partial application is a nice way to make handy functions from generic
ones. Imagine a generic converter.
*/
const converter = (unit, factor, input) => {
return `${(input * factor).toFixed(2)} ${unit}`;
}
// Lodash
const milesToKilometers = curry(converter)('km', 1.60936);
// Bind
const poundsToKilograms = converter.bind(null, 'kg', 0.45460);
console.log( milesToKilometers(5) );
console.log( poundsToKilograms(2) );
/*
That's it. In the near future we'll talk a bit about point-free style,
touch on pure functions (I don't think we need to go in depth here...)
and look at functional programming.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment