Curry and Function Composition

Eric Elliott
Nov 13, 2018 · 11 min read
Image for post
Image for post
Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)

What is a curried function?

// add = a => b => Number
const add = a => b => a + b;
const result = add(2)(3); // => 5

What is a partial application?

What’s the Difference?

What is point-free style?

function foo (/* parameters are declared here*/) {
// ...
}
const foo = (/* parameters are declared here */) => // ...const foo = function (/* parameters are declared here */) {
// ...
}
// inc = n => Number
// Adds 1 to any number.
const inc = add(1);
inc(3); // => 4
const inc10 = add(10);
const inc20 = add(20);
inc10(3); // => 13
inc20(3); // => 23
inc(3) // 4

Why do we curry?

g: a -> b
f: b -> c
// Algebra definition, borrowing the `.` composition operator
// from Haskell
h: a -> c
h = f . g = f(g(x))
const g = n => n + 1;
const f = n => n * 2;
const h = x => f(g(x));h(20); //=> 42
f . g = f(g(x))
const compose = (f, g) => x => f(g(x));
f . g . h
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);
const g = n => n + 1;
const f = n => n * 2;
// replace `x => f(g(x))` with `compose(f, g)`
const h = compose(f, g);
h(20); //=> 42

Trace

const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
/*
Note: function application order is
bottom-to-top:
*/
const h = compose(
trace('after f'),
f,
trace('after g'),
g
);
h(20);
/*
after g: 21
after f: 42
*/
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
/*
Now the function application order
runs top-to-bottom:
*/
const h = pipe(
g,
trace('after g'),
f,
trace('after f'),
);
h(20);
/*
after g: 21
after f: 42
*/

Curry and Function Composition, Together

const map = fn => mappable => mappable.map(fn);const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const log = (...args) => console.log(...args);
const arr = [1, 2, 3, 4];
const isEven = n => n % 2 === 0;
const stripe = n => isEven(n) ? 'dark' : 'light';
const stripeAll = map(stripe);
const striped = stripeAll(arr);
log(striped);
// => ["light", "dark", "light", "dark"]
const double = n => n * 2;
const doubleAll = map(double);
const doubled = doubleAll(arr);
log(doubled);
// => [2, 4, 6, 8]
f: a => b
g: b => c
h: a => c
f: a => b
g: (x, b) => c
h: a => c
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
const h = pipe(
g,
trace('after g'),
f,
trace('after f'),
);
h(20);
/*
after g: 21
after f: 42
*/
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);const trace = (label, value) => {
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
const h = pipe(
g,
// the trace() calls are no longer point-free,
// introducing the intermediary variable, `x`.
x => trace('after g', x),
f,
x => trace('after f', x),
);
h(20);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);const trace = value => label => {
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
const h = pipe(
g,
// the trace() calls can't be point-free,
// because arguments are expected in the wrong order.
x => trace(x)('after g'),
f,
x => trace(x)('after f'),
);
h(20);
const flip = fn => a => b => fn(b)(a);
const flippedTrace = flip(trace);
const flip = fn => a => b => fn(b)(a);
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const trace = value => label => {
console.log(`${ label }: ${ value }`);
return value;
};
const flippedTrace = flip(trace);
const g = n => n + 1;
const f = n => n * 2;
const h = pipe(
g,
flippedTrace('after g'),
f,
flippedTrace('after f'),
);
h(20);
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const traceAfterG = trace('after g');
const traceAfterG = value => {
const label = 'after g';
console.log(`${ label }: ${ value }`);
return value;
};
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
// The curried version of trace()
// saves us from writing all this code...
const traceAfterG = value => {
const label = 'after g';
console.log(`${ label }: ${ value }`);
return value;
};
const g = n => n + 1;
const f = n => n * 2;
const h = pipe(
g,
traceAfterG,
f,
trace('after f'),
);
h(20);

Conclusion

Learn More at EricElliottJS.com


JavaScript Scene

JavaScript, software leadership, software development, and…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store