Pure functions are at the heart of functional programming. The first step in learning how to write pure functions is to understand what makes a function impure or have side effects. For example, a function that alters variables outside of its scope is an impure function.
- First of all I said we need to talk about this notion of purity but we have to understand the opposite first, and the clearest and easiest way to understand what an impure function is, is to think about a function that produces a side effect. What is a side effect? A side effect, by virtue of its name, is something that occurs that's indirect as a relationship of an action that you took. So an action that you take, like calling a function and asking for it to compute some value and return it back, that's the direct effect of that function.
We get the value back. A side effect is basically when we change the state of the program through an indirect means. I call this function, I get a direct result back, I get some direct effect back, but I also changed something else about the state of my programs, and side effects are the big evil in functioning programming. Like the big big evil. That's why we're starting here. A functional programmer will tell you we got to reduce and remove as many side effects as possible. If you've ever worked in a language, I haven't done much but I've read some things about Haskell, and Haskell and other functional programming languages make it virtually impossible to do side effects.
The language is designed from the ground up to not even let you be able to do that. Which, to be honest with you, sort of twists my brain. I'm not entirely sure I understand whether that's valuable or even how that works, 'cause I haven't done, personally I haven't done a lot of Haskell programming. But it's this notion that this is so important that they bake that into the language. Now unfortunately there are some places where we kind of have to cheat. For example, input and output. Even something as basic as a console log statement to your console is, form a theoretical perspective, a side effect.
You've changed the state of what is happening. So input and output, pretty basic important. You're not going to get a ton of usefulness out of your programs if you have no notion of input and output. So even in those functional programming languages that draw a very distinct line and say you should not and cannot create side effects, they have this cheat, this side door cheat that allows them to do things like side effects on input and output, and they say, well that's just unecessary evil.
We don't want to program that way so we keep everything else pure and we just leave that over to its side and there's virtue and there's value in being very intentional and very explicit about what's pure and what's not pure, and virtually everything in a Haskell program is pure except for these few little cheats. So side effects, producing side effects can be things like the console log. It can be changing the state of some variable such that each time the function is called you're going to get a different result. That's also a side effect. What's interesting, there's a little bit of tension and I'll just sort of project a little bit till a little bit later in our discussion about closure.
Closure is about maintaining state inside of a function. When you maintain state in a way where that function can be called over and over again, and get a different result each time, that can start to, there's some tension there between that and this notion of side effects. So even the way that we put these Lego blocks together, we can have two very good Lego blocks that a functional programmer would say, yup those are both great, but there's a way to configure those but they're like eww, not so great. So we have to be careful about these things. Side effects are the big evil that we want to avoid.
So if we're looking for the big evil to avoid, this is just a quick illustration. You can see that I call foo. I pass in the value five and I have two side effects as a result of this function. I have the Y and the Z variable that are outside of the function. Now there's lots of fancy terminology for this but I'm trying to keep us away from the terminology. These are just variables in the global scope, and we're changing those variables, and by the way, they don't have to be in the global scope. There could be some wrapping function around this, and we could still be doing the exact same thing.
Which is changing variables outside. So the first time I call foo, I change the state of Y and Z forever. Permanently I've changed that state. So foo is said to be an impure function. Now, one of the interesting things that I've observed. Again, not coming at functional programming from being steeped in the academic tradition but trying to sort of come at it from the reverse. Like I'm a programmer and I need to get my hands around some of these tools. One of the interesting things that I've observed is when I look at functional programming, even though they tell you the evil is side effects, you should not have impure functions, everything should be a pure function, and I'll show you pure in just a moment, but what's interesting is, there's a whole bunch of impure functions in our programs.
A functional programmer will write many times impure functions, but the magic is, this again is just my takeaway. It's kind of like an observational takeaway. The magic is they never leave something impure publicly exposed. So if you have some action that fundamentally needs to be impure, it needs to have some sort of side effect because that's all you have available to you, the way you make it functional again, the way you make it pure is to wrap another function around that to contain all of those side effects within that function, and from the outside world that function itself, that outer function is pure.
So it's kind of like turtles all the way down. You know that principle? It's not actually pure all the way down. It really only has to be pure at the highest most level and under the covers it can be as ugly as you need it to be, and we'll see some examples of that. So if I had this impure function, as I show, this function on line one, this impure function, what can I do to make it more pure? How could I wrap it in something that would be more pure? Well I could, oh sorry before I get to that.
This is just another example of making those side effect changes. This is illustrating the fact that this change is happening over time. So when Y and Z have changed the first time, when I call foo on line 13, I'm making a change to the state that's not the same as the state was on line eight. So over time, and the reason why state change, by the way, the reason why state change is frowned upon, the reason why this side effect is frowned upon, is that makes code harder to reason about. It doesn't make it impossible to reason about.
Most of you that are not steeped in functional programming traditions have been very successful at writing very very stateful programs. Non immutable, non pure programs. You can get the same job done, but honestly it does make the program a bit harder to reason about. So what we're trying to strive towards is ways to use some of these techniques in patterns that will help us make more reasonable programming. Not things that we couldn't do before but things that we couldn't do as reasonable before.
So here the fact that I have to know from the outside that foo changes some state, and the first time I call it I'm going to get a different answer than the second time I call it, that's a complexity in my program that makes it harder to reason about. I have to trace the entire runtime stack to know how many times foo has been called before I can predict its output. If I could rearrange this program in such a way that every time I called the outer function I got the same result, I don't have that state complexity to worry about. So pure, of course, is no side effects.
What is a pure function going to look like? Well I'm going to wrap bar around foo. You notice foo is the same definition, but you notice what I'm doing with function bar declaration on line one, I'm passing in the entire universe. The whole universe that it's cared about I'm passing in the X and the Y and the Z state. I'm passing those things in and that means that every time that I call bar with the exact same universe, I'm going to get exactly the same answer back, and bar could be arbitrarily complex.
Bar cold be our entire program and we could have the entire state of our program that we put in and it churns a whole bunch of stuff and gives us an end result back, and every time we run the program we're going to expect to get exactly the same result. So what I've done is taken something that's fundamentally impure, the actions that I'm doing with foo, and I've made it pure by wrapping a wrap around it. Now the contract is not the same. You'll notice that I don't have independent free variables like Y and Z anymore. I get an array back with Y and Z in it.
As I'm returning on line three I'm returning an array with Y and Z because I don't have a way with the return key where to return multiple values other than to wrap it up in some sort of container, but it turns out that a functional programmer will tell you that's exactly what you want. You want some new value back that represents some new state, 'cause I can take that new state and run my program again with my new state if I cared to or I can discard that new state. I have those options, but I don't have to worry that this thing might have been called several times.
It might have changed state in weird ways that I can't understand. So creating a pure function, eliminating the external side effects can be as simple as simply wrapping a function around a whole bunch of impurity. Now we could have gone back and refactored foo itself and made foo pure, but I wanted to illustrate to you, and we will see this later in some of our exercises, that the notion of wrapping one pure function around one or more impure functions is actually something you see not terribly rarely in functional programming.
This course was created by Frontend Masters. It was originally released on 03/08/2016. We're pleased to host this training in our library.
- Pure functions
- Manual composition
- Composition utility
- List operations