Transcript from the "Side Effects" Lesson
>> Kyle Simpson: 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?
[00:00:20] A side effect by virtual 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 me function and asking for it to compute some value and return it back, that's the direct defect of that function.
[00:00:37] 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 change something else about the state of my programs.
[00:00:53] And side effects are the big evil in functional programming, like the big, big evil. That's why we start here. A functional programmer will tell you we've gotta reduce and remove as many side effects as possible. If you've ever working in a language, I haven't done much but I've read some things about Haskell.
[00:01:10] 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 because personally, I haven't done a lot of Haskell programming.
[00:01:31] But it's this notion that this is so important that they baked that into the language. Now, unfortunately, there are some places where we kinda have to cheat. For example, input and output, even something as basic as a console log statement to your console is, from a theoretical perspective, a side effect.
[00:01:51] You've changed the state of what is happening. So input and output, pretty basic important. You're not gonna 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.
[00:02:09] 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 a necessary evil. We don't wanna program that way, so we keep everything else pure, and we just leave that over to its side.
[00:02:26] 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.
[00:02:42] Such that each time the function is called you're gonna 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 to a little bit later in our discussion about closure. So, closure is about maintaining state inside of a function and when you maintain state in a way where that function can be called over and over again, and get a different result each time.
[00:03:07] There is some tension there between that and this notion of side effect. So even the way we put this Lego blocks together, we can have two very good Lego blocks that a functional programmer would say, yep, those are both great. But there is a way to configure those.
[00:03:21] Not so great, so we have to be careful about these things. Side effects are the big evil that we wanna avoid, so if we're looking for the big evil to avoid, this is just a quick illustration, we can see that I call through, I pass in the value 5 and I have 2 side effects as a result of this function.
[00:03:38] I have the y and the z variable. That are outside of the function. Now there's lot of fancy terminology for this, but I'm trying to keep us away from the terminology. These are just variables in the global scale. And we're changing those variables. And by the way, they don't have to be in the global scale.
[00:03:54] 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 changed the state of y and z forever, permanently. I have changed that state. So foo is set to be an impure function.
[00:04:10] Now, one of the interesting things that I have observed again, not coming at functional programming from being steep in the academic tradition but trying to sort of come at it from the reverse. I'm a programmer and I need to get my hands around some of these tools. Well, the interesting things that I observed is when I look at functional programming, even though they'd tell you the evil is side effects.
[00:04:34] 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 their programs. A functional programmer will write many times impure functions. But the magic is, this again is just my takeaway, it's kinda like an observational takeaway.
[00:04:55] 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.
[00:05:14] To contain all of those side effects within that function. And from the outside world that function itself, that outer function, is pure. 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 level and under the covers it can be as ugly as you need it to be.
[00:05:34] And we'll see some examples of that. So, if I have this impure function, as I've shown on line 1, this impure function. What can I do to make it more pure? How could I wrap it in something that would be more pure? 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.
[00:06:02] So when y and z have changed the first time. When I call foo online 13, I'm making a change to the state that's not the same as the state was on online 8. So over time, and the reason why state change, by the way, the reason why state change is frowned upon, the reason why the side effect is frowned upon, is that that makes code harder to reason about.
[00:06:25] It doesn't make it impossible to reason it out. 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 it out.
[00:06:45] So what we're trying to strive for towards is ways to use some of these techniques and 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 reasonably before. So here are the fact that I have to know from the outside that foo changes some state, and the first time I call it, I'm gonna get a different answer than the second time I call it.
[00:07:07] That's a complexity in my program that makes it harder to reason it out. 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.
[00:07:27] Okay, so, pure, of course, is no side effects. What is a pure function gonna look like? Well, I'm gonna wrap bar around foo. You notice foo is the same definition. But, you notice what I'm doing with function bar declaration on line 1, I'm passing in the entire universe.
[00:07:47] The whole universe that it's cared about and 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 gonna get exactly the same answer back. And bar could be arbitrarily complex.
[00:08:06] Bar could 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 gonna expect to get exactly the same result.
[00:08:20] So what I've done is take something that is 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.
[00:08:36] I get an array back with y and z in it. As I'm returning on line 3, I'm an array with y and z because I don't have a way with the return keyword to return multiple values other than to wrap it up in some sort of container. But turns out that a functional programmer will tell you, that's exactly what you want.
[00:08:54] You want some new value back that represents some new state cuz I can take that new state and run my program again with the 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 and might have changed state in weird ways that I can't understand.
[00:09:13] 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 re-fractured foo itself and made foo pure. But I wanted to illustrate to you, and we will see this later in some 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.