What is function composition?
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
Function composition is taking the return value of one function and passing it as an argument to another function. It's common enough that functional programmers have turned it into its own operation. In this episode, we go deep into why it's important and how you can use it and write it yourself.
Eric Normand: What is function composition? By the end of this episode, you will know what function composition is and why it's useful. My name is Eric Normand, and I help people thrive with functional programming.
This is an important topic because it's something we do all the time. We do function composition in both functional and non-functional styles of programming.
Functional programmers and mathematicians have named it, they've named it functional composition, and they've turned it into a higher order function. It's something that we tend to talk about probably more than you would in a non-functional language.
What is it? It's pretty simple. It's when you take a function and you call it, and then you take the return value of that function and you pass it to a second function as an argument. You're chaining the function calls.
Usually, in math or as examples, we use function f and g. You call function g, you get the result. You take that result and you pass it to function f. Of course, you get the result of function f.
There's a special case because sometimes you'll need some other arguments, like f will take it as the second argument. It will take the result of g as the second argument.
There's a special case where you take that return value of g and you pass it as the first and only argument to f.
Now, the g is the...You don't even need to name that return value, you don't need to save it into a variable. You pass it directly to f.
If you take this special case — because it's so regular — you can turn it into a higher-order function. That is, a function that takes f and g and returns a new function that does that same thing, that does the f open paren g.
Mathematicians usually use the dot as the operator for a function composition when they want to talk about it in a paper or something. They'll write, "f.g," to mean f composed with g. This gets confusing, and it's one of the reasons why function composition is thought of as difficult.
The confusing part is that the g is called before f, even though it comes to the right of f. If I do f.g, the g is called first, and then the f is called on the result of g. It looks backwards. It looks like, "Call f," and then, "Call g," but the g is called first. That makes it confusing.
The reason it is that way is, like I said, it's like doing f open paren g, open paren, put the args of g, and then close the parens. They're in the same order as if you were to call it directly, but for clarity, you might want to move the g out of there, because it's inside out.
The arguments get evaluated first before the function gets called on those arguments. If you move the g above there, and you called it g result equals g, and then you called it with its arguments, and then after that, you did f open paren g result...
I'm trying to talk through code, but the idea is that it's in the same order as something you should be familiar with. It's just when you look at it on it's own and that's not explained to you, it looks backwards.
Why do we talk about this? Why do we want to turn this into a higher-order function?
First of all, it's very common to do this. It's common to call a function and pass that argument to the next one. Even though it's less common than the general case, the special case of the single argument is still really common.
Let's say you wanted to double a number and then square it. You could double it, so you call the double function, then you take the result of that and you pass it to the square function, which then returns the value.
You could say, "This is a function composition," and you can call compose with the two functions. That gives you a new function, which is the double square.
What it does is it reduces boilerplate. By reifying it...I have a whole episode on reification if you want to know what that is. I also have a whole episode planned for higher-order functions, if you want to know more about that.
You're taking this idea that normally you're just doing code by embedding the call to g as an argument to f. You're now turning it into an object, a function, called compose, that can do that. Now, this thing can be manipulated like any other function, like any other value.
This compose function is not just a bit of syntax that your language understands. It's a function that your code can understand, that your program can use. Then you can pass it to other higher-order functions like map, and other stuff. You're getting leverage by turning it into a first-class thing.
I do want to say that it's not always clearer to do this, to use compose. It's something that people do use, though, and so, if you're doing functional programming, you should probably understand it. It's something that you'll see a lot.
One place where it really is used a lot is in a thing called point-free style. I have a whole episode planned on point-free style. Basically, point-free style is very simple, is a way of defining functions without naming the arguments.
I could write a function. I call it fg, or f.g, that's the name of the function. It takes arguments, those arguments get passed to g, and then g will return a value. That return value will get passed to f, and that return value f will get returned from f.g.
I can write that function, and I can write it over and over again for different fs and gs. I could write it for square and double, and I can write it for trim and capitalize on strings.
There's all sorts of examples that I could write by hand each time, or I could call compose, and not have to name the arguments, not have to repeat that boilerplate.
This point-free style is a way of building functions without naming arguments. To be very frank, arguments take up a lot of brain space, if you have to name the argument, you have to think of a good name, otherwise, it's going to be worse than not having one, and they take up code space.
You don't want to be naming them if you don't have to if it's not going to add to the clarity.
There's this thing called point-free style. Function composition is used a lot in it because you don't have to name the arguments.
Another cool thing about function composition is that it is associative and it has an identity. There's a whole episode on what an identity is. Notice if I do f composed with the identity function...
The identity function is just a function that returns its argument. You pass in the number five, it returns the number five. You pass in the string "Hello," it returns the string "Hello." It doesn't do anything to it.
You think, "What's the point of this function that doesn't do anything to it?" It's just like, "What's the point of zero?" It represents nothing. Representing nothing is actually very useful, and so is representing a function that does nothing. [laughs]
It's now able to be the identity value of function composition, so f composed with identity is the same as f. Identity composed with f is the same as f. The identity function is the identity value of function composition. You might want to write that down because it's a mouthful.
It's associative, which I have a whole episode on that. Real quick, it means the grouping doesn't matter.
Imagine I have f composed with g, composed with h. I can put the parentheses around the f and the g, or I can put the parentheses around the g and the h. I get the same thing. I get the same result. The function at the end of the day that I get from that whole big expression is going to do the same thing.
When you have something that's associative, and it has an identity, that means it is a monoid. I have an episode planned on monoids and what makes them cool.
That's just a cool thing I wanted to mention at the end. If you don't understand it, just wait for that episode and it will come.
This is the kind of thing that happens when you start dealing with functions as first-class objects, especially higher-order functions. You can start to get these kinds of cool algebraic properties that come out.
I just know when I've talked about this in the past, the really common question is, "Why do I need this? Why do you need it?" The truth is you don't need it. You can do manual function composition each time.
You can, but then the other question is, "Why do you need anything?" You can always just do for-loops and gotos if you want if your language has gotos these days.
Why do you use map instead of for-loop? Basically, if you can use a map, map is way less error-prone than a for-loop. You don't have to initialize any variables. You don't have to get the end condition right.
If you can use a map, it's probably less code than a for-loop. That's not really what matters. What matters is that it's less error-prone. The cool thing is that once you have map, map is a function. Map is something that can now participate in all these other higher-order functions.
Same with compose. You could do it manually, but then you don't have a function called compose that you can use in other ways.
This is part of, I would call it, the leverage of functional programming. Just by turning this thing that you do a lot into a function that can now participate in the rest of the ecosystem, you now have this leverage.
For now, you're acting in this higher level. Now, you're composing compose. You're mapping compose over things, which you couldn't do before. You could do it, but you'd have to write out compose each time you wanted to.
Let me recap. Function composition is simply when you take the return value of one function and pass it as the argument to the next function. You can imagine making a function that just does that. You'll realize that you do that over and over, that there's a common pattern.
You could just make a function that takes the two functions and returns that common pattern function. That one is called function composition. It's the special case.
Mathematicians use a dot. Even in Haskell, probably other languages, they use a full stop or the period character to indicate function composition, just because it looks like a dot. Because it's so easy to do, it's just a dot, you do it a lot.
It reduces boilerplate and then it reifies this concept into something that now you can leverage in with the rest of your functions. It's used a lot in point-free style and it's a monoid. It has an identity and it is associative.
You need to write a function called compose, let's say, that takes two functions as arguments, does the composition and returns the return value of them. It returns a new function that does the composition of the two.
You can look it up if you have to. If your language has first-class functions, it is really like a three-line function. It is nothing big. You don't have to do the full-blown, industrial-strength one that can handle multiple arguments and things like that.
Just a function of one argument, composed with another function of one argument. Just try that out. That will really help you understand what it's doing and why they're in the order they are.
That was a favor for you. Go ahead, do that for yourself. Learn a little bit deeper than what you can in a podcast.
I'm going to ask a favor for myself. If you liked this episode, please share it with your friends and please subscribe. If you share it with your friends, then it's just another thing to talk about with them.
You probably have coworkers who would like to know a little bit more about this functional programming stuff. If you subscribe, then you will get the next episode, which also has good stuff that you'll probably like if you liked this one, because that's how it is. I will have more stuff coming.
Thank you very much, and see you next time.