Functional Programming Part 3: Function Composition

Lecture 7 min.



Function Composition and Superposition

Functional Programming Part 3: Function Composition

Like all normal programmers, we are lazy. We don't want to keep building, testing, and deploying the same code that we rewrite over and over and over again.

We always try to find ways to reuse a solution to a problem we've already solved once, in other cases too.

Code reuse is a great idea, but in reality it's hard to achieve. Try to make your code too specialized and you won't be able to reuse it. Try to make it too generic and it'll be hard to use even for your original task.

What we need is a balance between these two positions, a way to create smaller, reusable pieces that we can use as building blocks to construct more complex functionality.

In functional programming, functions are our building blocks. We write them to solve specific problems, and then put them together, like Lego™ blocks.

The result of putting them together like this is called function composition.

So how does this work? Let's start with two JavaScript functions:

var add10 = function(value) {
    return value + 10;
};
var mult5 = function(value) {
    return value * 5;
};

That's a bit verbose, so let's rewrite it using arrow functions:

var add10 = value => value + 10;
var mult5 = value => value * 5;

That's better. Now let's imagine we need a function that takes a value, adds 10 to it, and then multiplies the result by 5. We could write:

var mult5AfterAdd10 = value => 5 * (value + 10)

Even in such a simple example, we wouldn't want to write a whole new function from scratch. First, we might make a mistake, such as forgetting the parentheses.

Second, we already have one function that adds 10 to a value and another that multiplies it by 5. So we'd be writing code we've already written.

So instead, let's use add10 and mult5 and put together a new function:

var mult5AfterAdd10 = value => mult5(add10(value));

We simply used the existing functions to get mult5AfterAdd10, but there's a better way.

In mathematics, f ∘ g is function composition (translator's note: also called superposition of functions) and is read as "applying function f to the result of function g" or, more simply, "performing f after g." So (f ∘ g)(x) is equivalent to calling function f after function g with the value x, or even more simply: f(g(x)).

In our example we have mult5 ∘ add10, or "mult5 after add10," which is where the name of our function mult5AfterAdd10 comes from.

And that explains what we did. We called mult5 after calling add10 with value, or simply: mult5(add10(value)).

Since JavaScript doesn't natively support function composition, let's take a look at Elm:

add10 value =
    value + 10mult5 value =
    value * 5mult5AfterAdd10 value =
    (mult5 << add10) value

In Elm, functions are composed using the infix operator <<. This lets us visually see how the parameters "flow" from one function into another. First, value goes into add10, and then its result goes into mult5.

Notice the parentheses in mult5AfterAdd10, specifically around the expression (mult5 << add10). They're there to make sure value is passed into the expression only after the functions inside it have been composed.

You can compose as many functions as you like, for example:

f x =
   (g << h << s << r << t) x

In this case, x is passed into t, whose result is passed into r, whose result, in turn, goes into s, and so on. If you wanted to do something similar in JavaScript, it would look something like this: g(h(s(r(t(x))))) — basically parenthesis hell.

Point-Free Function Notation

Point-free notation is a style of defining functions without explicitly specifying their input parameters. At first this style will seem unusual, but as you go on and get more comfortable with it, you'll come to appreciate the conciseness of this approach.

You'll notice that in mult5AfterAdd10, the value value is defined twice: once in the parameter list and once where it's actually used.

-- This function expects 1 input parametermult5AfterAdd10 value =
    (mult5 << add10) value

But this parameter is unnecessary, since add10, the rightmost function in the composition, expects the same parameter. The following point-free version is equivalent to the previous one:

-- This function also expects 1 input parametermult5AfterAdd10 =
    (mult5 << add10)

There are a number of advantages to using this point-free approach.

First, we don't need to define extra parameters. And since we're not defining them, we also don't need to come up with names for them.

Second, the code becomes easier to read and analyze, since it's more concise. This is a simple example, but imagine a function that takes more parameters.

Peculiarities of Point-Free Functions

So far we've looked at how function composition works, and how to define functions in point-free notation for conciseness, purity, and code flexibility.

Now let's try applying these ideas to a slightly different scenario and see how well they hold up. Imagine we replace add10 with add:

add x y =
    x + ymult5 value =
    value * 5

How do we create mult5AfterAdd10 using these two functions?

Well, if you actually spend some time thinking about this question, you'll probably come back with a solution like this:

-- This is wrong !!!!mult5AfterAdd10 =
    (mult5 << add) 10

But this code won't work. Because add takes two parameters.

If this isn't obvious enough in Elm, try writing the same thing in JavaScript:

var mult5AfterAdd10 = mult5(add(10)); // doesn't work

This code is wrong, but why?

Because in it, the function add receives only one of its two parameters, and then passes incorrect results into mult5. This will lead to wrong results.

In fact, in Elm the compiler won't even let you write such unreasonable code (which is one of the great strengths of the Elm language).

Let's try again:

var mult5AfterAdd10 = y => mult5(add(10, y)); // not point-free style

Yes, this isn't point-free style, but I can live with this solution nonetheless. At the same time, now I'm no longer simply combining functions. I'm writing a new function. Also, if the task became much more complex — for example, if I wanted to compose mult5AfterAdd10 with some other function — I would run into a serious problem.

As long as we can't "wed" these two functions, it will seem like the idea of function composition doesn't have all that much practical value. And that's a shame, because in reality it does.

Well, what would really help is if we had a way to pass our add function one of its input parameters ahead of time, and only later — once mult5AfterAdd10 is called — the second parameter.

The way out of this predicament is the concept of currying.

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Functional programming"

Terms: Functional programming