14/10/19

12 min de leitura

# Currying and Partial Application — Composition made easier

Rodrigo Botti

## Introduction

A few days ago I was talking to my best friend via text. We do this thing where we send code (or code-like) snippets to each other as a way to talk about everyday situations. For instance we might write `I.see(you).at(days.tomorrow())` instead of “see you tomorrow” (yes…it’s nerdy).

Anyways…last time I sent her a code snippet that contained a `curry` statement around some functions. That raised some questions which I proceeded to answer directly in chat. Not to pat myself on the back but I really liked the way I explained it (so did she) and I decided to write an article about it (the following quotes and links from other sources were not part of the original conversation). The question that started it all was:

“What is curry? Is it what I’m thinking?”

Note: I’ll be using Javascript code snippets to explain the concepts this article intends to present. I’ll assume you have some javascript knowledge — syntax and core functions at least.

## Higher Order Functions

Before jumping right into explaining currying and partial application, first we need to understand what higher order functions are.

“A higher order function is a function that takes a function as an argument, or returns a function.” — Eric Elliot

Let’s take a look at some examples of higher order functions from Javascript’s built-in Array type. Array instances have the methods mapfilter and reduce.Each is a higher order function that receives another function as an argument.

``````const numberArray = [1, 2, 3]

const double = val => val * 2
const doubled = numberArray.map(double)
// > [2, 4, 6]

const isOdd = val => val % 2 !== 0
const odds = numberArray.filter(isOdd)
// > [1, 3]

const add = (x, y) => x + y
// > 6``````

As you can see, the functions take a function as a parameter. But why? Why not just loop over arrays? As it turns out, transforming each element (mapping), filtering/removing elements (filter) and accumulating the elements (reduce) of an array are common operations. These are higher order functions, abstract iterations where something happens to each element (transformation, predicate evaluation, and accumulation respectively).

What do we mean by that? Imagine now you want to only get even numbers from the array. All you have to do is write a predicate function that identifies even numbers and use .filter. In an imperative “for-loop” implementation we would have to rewrite the whole loop just to change the “if” condition in it.

## Curry

“A curried function is a function that takes multiple arguments one at a time.” — Eric Elliot

Let’s say we have a two parameter function that adds two numbers:

``const add = (x, y) => x + y``

We would say add is curriedwhen it is in the form

``const add = x => y => x + y``

Note that it is a function that (takes a single parameter) returns another function that (takes a single and the last parameter) finally returns the result i.e. add is a higher order function since it returns a function.

Why do that? To achieve something called partial application.

Consider the curried add function above. We could do something like this:

``const increment = add(1) ``

The increment function is the add function but with x = 1! Which means that when we call:

``````increment(2) // 3
increment(5) // 6``````

## partial application

Now imagine that extended to function of N arguments where any number of them can be partially applied. This is partial application.

“A partial application is a function which has been applied to some, but not yet all of its arguments. In other words, it’s a function which has some arguments fixed inside its closure scope. A function with some of its parameters fixed is said to be partially applied.” — Eric Elliot

Notice the difference? Curried functions take one argument at a time whereas partially applied functions can take many arguments at a time.

Some functional programming libraries give you the power to turn a function of many arguments, making it possible to have it partially applied.

Something that can cause confusion: these libraries usually give this function the name curry(instead of partial, for example). One such example is ramda and its curry function:

``````const { curry } = require('ramda')
const add = curry((x, y) => x + y)

Note: we can call it with one argument at a time or both at the same time whereas in the previous curried case we have to call it one at a time exclusively.

Now let’s think about having a function with three arguments:

``````const a = curry((x, y, z) =>
x + y + z
)
// because `curry` allows for partial application:
a(1)(2)(3) === a(1, 2)(3) === a(1)(2, 3) === a(1, 2, 3) // true``````

Side note — you could implement your own version of curry like this:

``````const curry = (fn, n) => {
const arity = n || fn.length
return (...params) =>
params.length >= arity
? fn(...params)
: curry(
(...rest) => fn(...params, ...rest),
arity - params.length
)
}``````

Now going back to the subject of partial application. Why would we do something like that?

## easier composition

Problem: Let’s say we want to calculate the sum of the age of all admin users in a list of users and a user is represented by an object in the form:

``````{
age: Number, // user's age
// other properties omitted for brevity
}``````

Let’s first talk about function composition (in a loose way).

``````// Assume we have two functions f1 and f2
const composed = x => f2( f1( x ) )

// Now assume we have functions f1, f2, ...fn
// In pseudo-code:
const composed = x => fn( ... ( f2 ( f1 ( x ) ) ) )

// Note the execution order is RIGHT-TO-LEFT
``````

We could write a function to compose many functions and return the composed function.

``````// composes many functions
const compose = (...fns) => x =>
fns.reduceRight((y, f) => f(y), x)

// now we can have, for instance
const composed = compose(f2, f1) // x -> f2(f1(x))``````

Note: Functional programming libraries usually have compose already implemented — e.g. ramda‘s compose.

Back to our problem. We’re going to need to enlist the help of some functions: our good friends the Array Higher Order Functions. Only this time we’ll have them partially applied:

``````const map = curry((mapper, array) =>
array.map(mapper)
)
const filter = curry((predicate, array) =>
array.filter(predicate)
)
const reduce = curry((reducer, initial, array) =>
array.reduce(reducer, initial)
)``````

Let’s also use the good old add function:

``const add = curry((x, y) => x + y)``

Cool. Now with all of these tools in hand, let’s create our function to calculate the age sum of admin users.

First, we need some helper functions pertaining to the domain of the problem:

``````const isAdmin = user => user.admin

const getAge = user => user.age``````

With that we can write our function:

``````// [User] -> Number
reduce(add, 0),  // [Number] -> Number (sum)
map(getAge),     // [User] -> [Number] (ages)
)``````

Look at that! By taking these small steps we have our complete function. Each small step is a partially applied function.

We can still improve it. Note how `isAdmin` and `getAge` are similar. They follow the same pattern: access a field in an object. Let’s turn that into a (guess what) curried function.

``````const prop = curry((field, object) =>
object[field]
)

// that way we can rewrite with partial application:
const getAge = prop('age')    // user -> user['age']``````

Another small refactor — summing an array of numbers seems like it deserves its own function:

``const sum = reduce(add, 0)``

That way we have the final version:

``````const sumAdminAges = compose(
sum,
map(getAge),
)``````

Why is this cool? Well, let’s look at some alternatives.

#### 1 – Imperative

``````const sumAgeAdmins = (users) => {
let sum = 0
for (const user of users) {
sum += user.age
}
}
return sum
}

// or (arguably worse)
const sumAgeAdmins = (users) => {
let sum = 0
for (let i = 0; i < users.length; i ++) {
const user = users[i]
sum += user.age
}
}
return sum
}``````

#### 2 – “Fluent” Functional

``````const sumAgeAdmin = users =>
users
.map(user => user.age)
.reduce((acc, curr) => acc + curr, 0)

// or
users
.map(getAge)

I’ll let you judge which one you prefer.

Of course, each has its place.

## Closing thoughts

The function composition approach is the easiest to read in my opinion. It’s declarative, each step or transformation is clearly laid out, and it’s easy to understand the problem it’s trying to solve. Also, it’s the easiest to add features to: sometimes it’s as easy as composing with another function (i.e. adding another function to the compose call).

However, it can also be the hardest to debug.

You probably noticed that on the “domain” functions we defined there’s a lack of reference to the function parameters:

``````const getAge = prop('age') // user is not referenced

compose(
// ...
filter(isAdmin) // array of users is not referenced
) ``````

This is called point-free style. As you can see, given that our functions basically have no reference to parameters or even a body, there’s nowhere to add breakpoints, which might lead to “print debugging” or temporarily removing the “point-free-ness” just to add breakpoints:

``````compose(
// ...
users =>                  // now I have all these
filter(user =>          // lines to add a breakpoint to
isAdmin(user), users  // and inspect the values
)
)``````

I’ve done that a few times… (and I sometimes forgot to “re-point-free-ify” the functions prior to committing the code…oh well).

But I also said it was easy to add features, so let’s add a way to “print debug” our functions:

``````const log = curry((label, value) => {
console.log(`[\${label}]`, value)
return value
})``````

We wrote a function that logs a value with a given label then returns the value.

Because of that, we can compose without any trouble:

``````const sumAdminAges = compose(
sum,
log('Ages'),         // [Number] -> [Number]
map(getAge),
log('Admin users'),  // [User] -> [User]
log('List of users') // [User] -> [User]
)``````

Notice that now we can log the intermediate lists passed from function to function.

But what if we wanted to log each user that is being fed to the `getAge` function? Composition of course!

``````compose(
// ...
map(compose(getAge, log('get age from user')))
// ...
)``````

Now we can “debug” and everything is still declarative (and point-free).

The imperative way, again, in my opinion, is the hardest to understand. Not only has it nested blocks, but it also has mutable state (let sum = 0) which leads to scattered logic (in the most nested loop you have to mutate a variable declared way up).

Note: This was a simple example, but keeping track of mutable state can be incredibly hard depending on the situation, especially when concurrent execution is involved.

## conclusion

That just about ends my take on curry, partial application, and function composition.

I must admit, the texts I sent my friend were a lot more informal and didn’t have all the references and quotes to Eric Elliot’s amazing posts. I also skipped on the higher-order function part but I thought it was a good idea to include it in here!

So let’s recap:

Higher-order functions are functions that take functions as parameters or return functions when called.

Curried functions are higher-order functions that take one argument at a time returning a series of functions until all parameters are passed.

Partial application is when a function has been supplied some of its parameters.

Function composition can be made easier by using curried functions and partial applications.

• Eric Elliot’s Composing Software. I used a lot of quotes from his series on functional programming. It is an amazing series of posts and book. It explains a lot more than I did and has plenty of chapters on other functional programming concepts.
• Functional programming libraries usually have the curried versions of mapfilterreduce already implemented.
• You may have noticed that all array higher order functions have the array as the last parameter. That is called “data last” style so you can curry and partially apply for easier composition.
• In case you’re starting down the functional path and want to try it with Javascript, I recommend starting with ramda as it has plenty of helpers for dealing with collections, functions and objects in a functional way (without introducing more complex concepts such as ADTs, Functors, Monads, etc — if you want some of that check out CrocksFlutureSanctuary and the Fantasyland spec)
• I would really like to thank my friend, Thaissa Candella for, first of all, asking the question that started it all, for being nerdy with me and for encouraging me to write the article in the first place. You’re the best!
• I would also like to thank all my colleagues for taking the time to read and review my draft: André KanayamaAdrian ShiokawaDeromirLeonardo CardosoLetícia Tiveron and Paulo Duarte. Thanks for the support, friends!

Here’s the complete code we produced in this post (also available here):

``````// Helper functions

const compose = (...fns) => x =>
fns.reduceRight((y, f) => f(y), x)

const curry = (fn, n) => {
const arity = n || fn.length
return (...params) =>
params.length >= arity
? fn(...params)
: curry(
(...rest) => fn(...params, ...rest),
arity - params.length
)
}

const log = curry((label, value) => {
console.log(`[\${label}]`, value)
return value
})

const prop = curry((name, obj) =>
obj[name]
)

// Array functions

const map = curry((mapper, list) =>
list.map(mapper)
)

const filter = curry((predicate, list) =>
list.filter(predicate)
)

const reduce = curry((reducer, initial, list) =>
list.reduce(reducer, initial)
)

// Domain functions

const add = curry((x, y) =>
x + y
)

const getAge = prop('age')

sum,
log('Ages'),
map(getAge),
log('List of users')
)

// execution

const users = [
{ admin: true, age: 30 },
{ admin: false, age: 45 },
{ admin: true, age: 40 },
{ admin: false, age: 80 },
]

// [List of users] [ { admin: true, age: 30 },
//   { admin: false, age: 45 },
//   { admin: true, age: 40 },
//   { admin: false, age: 80 } ]
// [Admin users] [ { admin: true, age: 30 }, { admin: true, age: 40 } ]
// [Ages] [ 30, 40 ]
// 70``````

Nós usamos cookies para melhorar sua experiência no site. Ao aceitar, você concorda com nossa Política de Privacidade