I am a big fan of functional programming. Functional programming is based on the idea of composing functions with no side effects, rather than a series of imperative statements, making it predictable and easy to understand on a micro scale. It helps make code reusable too as a benefit.
Functional programming works well with modern web frontend approaches such as React, as immutable state is as the core of React's declarative API.
There are some great libraries for assisting with functional programming such as ramda. It has a vast array of utilities at your disposal. One criticism of functional programming is that it's got quite a high learning curve, and it can be taken too far, reducing understandability of a program. Codebases heavily relying on functional programming may also be a bit jarring for newer developers who aren't used to functional style. However once you have got to grips with it, I've found it hard to go back.
In this post I will go through how to create some functional programming utilities which I personally find I use almost every day. It's always helpful to go back to basics sometimes, to understand how the things you use all the time actually work.
Partial function application
In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.
What on earth does this mean?
Learning through example is often the quickest way (for me at least) to understand problems like this.
Let's say we're doing some maths in our computer program, and want to add three numbers together. We could define a pure function that accomplishes this.
const add = (a, b, c) => a + b + c;
add(1, 2, 3) // 6
This is great, now we have a reusable function we can use throughout our program.
So how does partial function application relate to our example? "fixing a number of arguments" refers to capturing values in arguments through the use of closures.
To create a version of this function which we can partially apply, we need to create a new inner function. It should have access to the outer function's arguments, and the outer function should return the inner function.
For our adding example:
const partialAdd = (a, b) => (c) => a + b + c;
const boundAdd = partialAdd(1, 2); // (c) => 1 + 2 + c
boundAdd(3); // 6
boundAdd(1); // 4
Now we can provide 2/3 of the arguments, giving us the ability to provide the last argument at a later point in time.
Generic partial function factory
In our adding example, we created a function that had two of the first arguments fixed, creating a new function which expects a single argument, which could access the original two arguments.
This is fine, so long as we always want to apply two arguments, then apply with a third argument. What if we wanted to apply a single argument, followed by the next two arguments though?
We could rewrite the function to achieve this, but is there any way to convert any function into a function which can be partially applied with any number of arguments at any point?
It turns out there is, and this is an example which achieves this:
const partialFactory = (fn) => {
// Determines how many arguments the given function expects
const fnArity = (fn) => fn.length;
const doPartial = (args) => {
// If we've got enough arguments, we can apply the
// original function
if (args.length >= fnArity(fn)) {
return fn(...args);
}
// Otherwise return a new function expecting more arguments
return (...nextArgs) =>
doPartial(args.concat(nextArgs));
};
return doPartial([]);
};
Using this factory, we can convert our original add
function into a new function that
keeps returning functions until all the arguments that add
expects have been supplied.
For example:
const partialAdd = partialFactory(add);
// All of these work and result in same value
partialAdd(1)(2)(3); // 6
partialAdd(1, 2)(3); // 6
partialAdd(1)(2, 3); // 6
partialAdd(1, 2, 3); // 6
partialAdd()()()()()()()()()(1, 2, 3); // 6
Pretty cool!
The only caveat to this partial application factory, is that
fn.length
must be known, so functions utilising the arguments
object instead
of explicitly defining them, or functions using spread syntax for its arguments
will not work.
Libraries like ramda have this covered though, take a look at curryN.
Currying
A curried function is a special form of partial function.
Partial application allows you to supply an arbitrary number of arguments for partial application. Curried functions are special in that they always only accept 1 argument.
Referring to our adding example again, the curried form would look like this:
const curriedAdd = (a) => (b) => (c) => a + b + c;
To apply this function, we need to keep applying the resulting functions, one argument at a time
curriedAdd(1)(2)(3); // 6
Generic curried function factory
We can also create a generic utility for creating curried versions of functions. We can in fact re-use the implementation we created for partial application with no changes, and it would work just fine.
const curriedAdd = partialFactory(add);
curriedAdd(1)(2)(3); // 6 as expected
The only problem, is that curriedAdd still accepts any number of arguments at each application stage.
We can be more specific, and create a similar factory which strictly enforces curried functions only, like so:
const curriedFnFactory = (fn) => {
const doPartial = (args) => {
const fnArity = (fn) => fn.length;
// If we've got enough arguments, we can apply the
// original function
if (args.length >= fnArity(fn)) {
return fn(...args);
}
// Otherwise return a new function expecting another argument
// Note we return a function expecting a single argument,
// rather than an arbitrary number of arguments
return (...nextArgs) => {
if (nextArgs.length !== 1) {
// Enforce only one argument
throw new Error(
'Curried functions only accept one argument at a time'
);
}
return doPartial(args.concat(nextArgs));
};
};
return doPartial([]);
};
Now we have a curried form of add, that will throw an error if you try to apply it with any number of arguments except 1.
const curriedAdd = curriedFnFactory(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // Error
curriedAdd(); // Error
Why is partial application useful?
It's hard to see from these contrived examples why partial application and currying are useful. It's unlikely you would ever need to define a function for adding numbers, seeing as this is such a simple operation.
Partial application is extremely useful in day to day development however. You have probably used it without even thinking of it as "partial function application".
Say you have a React application, where you have some entities in your application's state, such as items in a shopping cart. Say you want to add a single item's price to the total with a button click.
const MyContainer = () => {
// Using redux for state, retrieve the items from state,
// details not important
const items = useSelector(state => state.items);
// redux dispatch
const dispatch = useDispatch();
const addToTotal = (itemId, itemCost) => {
// Imagine we have an action creator to handle
// this, the details do not matter
dispatch(addToTotal(itemId, itemCost));
};
return items.map(item => {
return (
<TotalUpdater
key={item.id}
item={item}
addToTotal={addToTotal}
/>
);
});
};
const TotalUpdater = ({ item, addToTotal }) => {
const onClick = () => {
addToTotal(item.id, item.cost);
};
return (
<div>
{item.title}
<button
type="button"
onClick={onClick}
>
Add item "{item.title}" to total
</button>
</div>
);
};
This works fine, but you may have noticed that we're passing a lot of
information into a simple button-and-label component. There's no reason why
a button needs to know if the shopping cart items exist, how to
get the prices from them, or anything about them. All TotalUpdater
needs
to know, is what to do when the button is clicked. It doesn't need to know the
costs or details about the items, other than string labels.
Implementing this idea into our LabelledButton
(renamed from TotalUpdater
)
gives us the following:
const LabelledButton = ({ label, buttonLabel, onClick }) => {
return (
<div>
{label}
<button
type="button"
onClick={onClick}
>
{buttonLabel}
</button>
</div>
);
};
Now the button-and-label can be used anywhere you need this, rather than only being applicable to shopping cart items.
How does this change the container component though?
We will need to partially apply, or fix arguments into the
addToTotal
function, containing details about the items for this
to work - a form of partial application!!
const MyContainer = () => {
const items = useSelector(state => state.items);
const dispatch = useDispatch();
const addToTotal = (item) => () => {
// We have have bound `item` within the new closure for
// the returned function
dispatch(addToTotal(item.id, item.cost));
};
return items.map(item => {
return (
<LabelledButton
key={item.id}
label={item.title}
buttonLabel={`Add item "${item.title}" to total`}
onClick={addToTotal(item)}
/>
);
});
};
Partial application here has made the component far more reusable, encapsulating details about the shopping cart items in the container component. I guarantee you will see many use-cases in day to day development if you look out for components that have too much detail leaking into them.
Composition
At the core of the philosophy of functional programming is the ability
to compose a series of functions together to create an output. Without any
utilities, function composition can quickly become unreadable. You migh
have even seen or used the compose
utility that redux includes in its API (compose),
as composing multiple middlewares together when configuring a redux store
is a very common problem.
As an example, if you have three curried mathematical functions you wish to combine:
const add = (a) => (b) => a + b;
const multiply = (a) => (b) => a * b;
const subtract = (a) => (b) => a - b;
You could combine them together like so:
const result = add(1)(multiply(2)(subtract(3)(1))); // 5
// Equivalent to 1 + (2 * (3 - 1))
This works, but clearly isn't very readable with so many braces all on the same line. You can try to make it more readable by breaking onto new lines for each function application which kind of works.
const result = add(1)(
multiply(2)(
subtract(3)(
1
)
)
);
What happens when I want to combine 4, 5, 6 functions together?
compose
Enter the compose
utility. This function allows you to compose multiple
functions in the same way as we saw before, but without needing to nest
the function-applications, making the code much more readable.
This is what we want our compose
function to be able to do, which is identical
to how redux's compose API works.
// Same math functions as earlier
const add = (a) => (b) => a + b;
const multiply = (a) => (b) => a * b;
const subtract = (a) => (b) => a - b;
const doMaths = compose(
add(1),
multiply(2),
subtract(3),
);
doMaths(1) // 5
This is clearly far more readable than the first example.
The questions is, how does compose
actually work, and how could we implement
it?
const compose =
(...fns) =>
(initial) =>
fns.reduceRight((acc, fn) => fn(acc), initial);
This is actually all that's needed. We iterate over all the given functions
from right to left, returning the result as the accumulation in reduceRight
,
passing the accumulation to the next function, until they have all been
applied.
Seeing it expressed like this can still be hard to follow if you are not used to functional programming (at least it was hard for me to follow!). It can be helpful to step through what happens at each stage. We are going to pretend we are doing the execution part of a JavaScript interpreter.
// How we use compose in our above example
const doMaths = compose(
add(1), // Returns (b) => 1 + b,
multiply(2), // Returns (b) => 2 * b,
subtract(3), // Returns (b) => 3 - b,
);
// Inside compose upon application:
const compose =
// We recieve an array of partially applied functions as `...fns`.
// This is what we have after accounting for the variables
// being bound to the argument `a` in each case
// [
// (b) => 1 + b,
// (b) => 2 * b,
// (b) => 3 - b,
// ]
(...fns) => ...
// Applying compose with these functions returns a new function:
(initial) => [
(b) => 1 + b,
(b) => 2 * b,
(b) => 3 - b,
].reduceRight((acc, fn) => fn(acc), initial);
// Applying this new function with an initial value of 1:
(1) => [
(b) => 1 + b,
(b) => 2 * b,
(b) => 3 - b,
].reduceRight((acc, fn) => fn(acc), 1);
// Step 1: inside reduceRight
// reduceRight callback with initial value:
(1 /* acc */, (b) => 3 - b /* fn */) => ((b) => 3 - b)(1) /* reduction function body */
// Replacing b in the function application:
3 - 1 // 2
// Step 2 inside reduceRight
// reduceRight callback with result of step 1:
(2 /* acc */, (b) => 2 * b /* fn */) => ((b) => 2 * b)(2) /* reduction function body */
// Replacing b in the function application:
2 * 2 // 4
// Step 3 inside reduceRight
// reduceRight callback with result of step 2:
(4 /* acc */, (b) => 1 + b /* fn */) => ((b) => 1 + b)(4) /* reduction function body */
// Replacing b in the function application:
1 + 4 // 5
// That's it, result is 5!
pipe
pipe
is exactly the same as compose
, except the order of application
is reversed. It can be easier to read and understand when the order of the
composed functions is the same as the order of application.
Implementation:
const pipe =
(...fns) =>
(initial) =>
// Note `reduce` is used instead of `reduceRight`,
// as we iterate the functions left to right instead
fns.reduce((acc, fn) => fn(acc), initial);
Now if we apply the same maths functions, we'll end up with -1
instead:
const doMaths = pipe(
add(1),
multiply(2),
subtract(3),
);
doMaths(1); // -1
// Equivalent to 3 - (2 * (1 + 1))
Summary
We looked at why we might want to use partial function application, some basic examples of how to compose functions, and how to create some functional programming utilities.
I hope this article has helped you understand functional programming a little better, and that you will make use of it in real life scenarios!