Transcript from the "Pure Functions and Side-Effects" Lesson
>> Kyle Simpson: All functional programming, discussions, after we've motivated the discussion in itself. All functional programming discussions need to start with the function. We have to understand what function is before we have any hope of understanding what functional programming is really trying to get for us.
>> Kyle Simpson: So here's a code sample.
[00:00:21] I've got a foo function that prints some stuff. It's got a console.log statement and it prints some numbers to it. And then I have a bar function, which calls that foo function. And I'm deliberately using some features here that are ES6, and they're maybe not terribly familiar to you.
[00:00:41] If you see on line 5, x = 2, that's the default parameter syntax that was out in ES6. The ...args there on line 5 is gathering up the rest of the parameters into an arguments array. That's called the rest or the gather operator. The ...args on the next line is the reverse of that.
[00:00:59] It's spreading an array out. Spreading the args array out as individual positional parameters. So, and same thing down on line 14, we're spreading out that array as individual arguments. If those features aren't terribly familiar to you, I'm gonna strongly recommend you take a little bit of time to read about some of the stuff that's been added to ES6.
[00:01:20] Because functional programmers want to use as much as possible what the language gives us. So that we're not dealing with the imperative stuff, and we're focusing at the higher level of declarative. And that really is what ES6 was all about. The most important parts of ES6 were about creating declarative boundaries instead of hyper focusing on imperativeness.
>> Kyle Simpson: So just I wanted to call that out in case those things look a little unfamiliar to you, I'm gonna be reusing them across all the rest of the slides. Fortunately, I've written a book about ES6, it's in the You Don't Know JS book series, that's also available on GitHub.
[00:02:00] So if you're struggling to understand some of that ES6 stuff, feel free to take a look at that, specifically chapter two. And yes, we do also have a course here on Frontend Masters called ES6 The Right Parts, where we cover a lot of that same stuff.
>> Kyle Simpson: But what's going on here?
[00:02:19] Are foo and bar both equally deserving of the label function? They both have the keyword function in them. But does that make them functions because they use the function keyword? I'll just flatly say no. I'll just answer that very bluntly. No, as a matter of fact foo most definitely is not a function from the perspective of a functional program.
>> Kyle Simpson: What about bar? Is bar a function? Well, it does in essence seem to be doing what we want a function to do. There is a return keyword there. We are using the inputs that have been passed in. But because bar calls a foo, and foo is not a function, I would say, if you have a function, a real function, that calls something that's not a function.
[00:03:14] That sort of pollutes the fact that the original thing was a function. In a sense, it does kinda need to be turtles all the way down. So even though bar in isolation might look more like a function, the fact that it calls something which is most definitely not going to meet that definition, means that we really can't call bar a function.
[00:03:39] Now it does actually do some stuff, but there's another term for that. The term for an arbitrary collection of operations is a procedure. We use the same keyword here. We use the keyword function for both things that are procedures, collections of operations, and functions. And as a matter of fact, if I drew the Venn diagram here, all functions are procedures but not all procedures are functions.
[00:04:12] Got it? The Venn diagram.
>> Kyle Simpson: [COUGH]
>> Kyle Simpson: So what is it mean to be a function?
>> Kyle Simpson: This one most definitely is a function by that definition and I wanna talk about what's different with this foo. This foo takes some inputs, it does some operations on those inputs and importantly it returns an output.
[00:04:45] At the most based sense, I could say that a function to be real function and not just a procedure, must have a return keyword in it. A function without a return statement Is not a function, it's a procedure. And it really needs to ideally return something affirmative and useful.
[00:05:11] Not just an early return like return semicolon, which you are just escaping your flow control early. Ideally, it needs to actually return a value. That in the most pure sense that's what we mean by function.
>> Kyle Simpson: Okay, now I promised we weren't gonna get too much into the notation.
[00:05:31] But I just need to dip our toe back into some math just for a brief moment. Hopefully it won't be too traumatic to many of you. Do you remember back, with fondness I'm sure, to your days in elementary school, middle school, high school, when you were learning about things like algebra.
[00:05:51] And you learned about equations like this. And this was, if you remember, this was f (x), the function f (x) is defined as 2x squared + 3. And then you're like, well, solve for x, I mean solve for f(x). Put in an x value. Compute the result. If I put in the value 3, I'm gonna get 21 out.
[00:06:15] The value 3 gets squared to 9. We multiply it by 2 then we get 18. We add 3 to it and we get 21 out. So there was this sense of f (x) = 2x squared + 3. And sometimes you'd even write it as y = 2x + 3 and they'd say solve for y.
[00:06:33] Or then they'd give you the y and say solve for x and you had to adjust this stuff around. I remember in my math class how deeply abstract that felt. I'm just moving these weird symbols around and I'm not one of those people that particularly loves to deal with abstracts.
[00:06:52] I like to work with concrete stuff a lot better. And so when I was working with these abstract things, I could do it. I mean, I was adept at math. It was not one of the topics that scared me, and to some people it does. And I totally get that, I understand if math was intimidating to you.
[00:07:09] Math came naturally to me but even though it came naturally to me, this was still uncomfortable for me to deal with abstract stuff. And it only got worse when I got to calculus, and we were dealing with all these weird symbols that I didn't know and then I, I mean, I went full into it.
[00:07:24] I took differential equations and linear algebra and all kinds of crazy stuff. And I never really liked the abstract part of math. Which is how I figured out in college I better not go be a math major because that's all it is. It just gets more and more abstract as you go along.
[00:07:42] But one of the things that I loved about,
>> Kyle Simpson: The algebra side of this is that at some point in the class, the teacher said, well, one way to think of what this thing is doing is to plot out all those input outputs as points on a graph.
[00:08:00] I don't know if anybody remembers working on the old TI-85s and plotting the graphs to your equations. And well, we never used the TI-85 for graphing, did we? We used it to play games. Let's just all be honest. But if you remember, we plotted out the points. And so if we stick in that x value of 3, which is over there on the right-hand side of our x-axis, you see that we spit out 21.
[00:08:25] And that's roughly corresponds to where we see that part of the curve up around the y value 21. So these inputs represented x values and the outputs represented y values. So what we did was we said here's this abstract equation. And we put an input in, we got an output out.
[00:08:43] We took those pairs of input outputs. And we assigned them a semantic meaning, which is that they're points on a curve, or a line, or whatever. And if we put enough of those together, we can start to visualize what this thing is doing. And that's what made me start loving that part of math again, when I could take something that was abstract and start to make something more concrete of it.
[00:09:08] And then I go all abstract again when I got into calculus. And they were talking about derivatives, I'm like I don't know what a derivative means. I know that x squared is the derivative is 2x, but I don't know what that means. And then the teacher was like it's just the line that's tangent to the curve.
[00:09:24] And I was like now I have a thing to visualize. It was concrete again. And then we got to integrals and I was like my god. What on Earth is this little symbol? And I don't understand this. And the teacher is like it's just the area underneath the line.
[00:09:37] And I was like it's concrete again. So it was this back and forth. And I basically gave up on math when I stopped being able to go back to the concrete part. Cuz the abstract part was not my strength. But I liked the fact that I could deal with something in the abstract and always connect it back to a concrete intuition.
[00:10:22] I would have to say f(x), do some calculations on it, and return that value. That's how I would implement an actual real function.
>> Kyle Simpson: So what about this code? Looks similar. I've got an f function. It doesn't have an input, but I am using an x. And it doesn't have a return statement, but I am assigning it to a y.
[00:10:46] So is this a function? It does seem to be the, I could use it in a sense to get my x and my y pairs. Is this gonna be a function? Well, actually, not really. Not really, because this is using what we referred to disaffectionately as side effects.
[00:11:08] Specifically, there's two different ways to think about a side effect, x, here. We use x on line 2. x is not an input to the function or at least that's not a direct input to the function. It's not a parameter. It's not an argument that we pass in.
[00:11:26] It is a variable that we access to use as an indirect input.
>> Kyle Simpson: That's what I like to call a side cause. It's not a direct cause. A direct cause versus a direct effect would be a direct input versus a direct output. We see no direct input here, rather we see a side input or a side cause.
[00:11:49] Moreover, we do not see any direct output. We don't see a return statement. Rather what we see is a side effect, which is the output into a variable instead of through the return statement. So while I still have the general sense that I'm computing data here, I've violated one of the fundamental tenants of functions and indeed a functional programming.
[00:12:15] And that, I've allowed these side causes and side effects to be part of the implementation. Now, [COUGH] I used to think that when functional programmers got all up in arms about side effects, that it was mostly just throwing stuff out there academically to make yourself sound smarter, that it didn't really matter.
[00:12:37] And I think one of the reasons for that is, I don't have any proof for this, but I'm pretty sure just the natural state of all programmers is to write with side effects. I think that's just our natural instinct. I don't know, cuz I don't write Haskell. But I kinda think people in Haskell probably also would like to write side effects and they are just not allowed to, cuz the language doesn't let it happen.
[00:13:01] But I think especially for the rest of us not writing in a language that restricts that is just our natural state of being to think about using stuff, and setting things over here, and all that stuff. So I think this is our natural state of affairs. But that is not what the functional programmer says.
[00:13:20] The functional programmer says this is bad for us. Now, why is it? Why is this side effect, why are these side causes bad? Is it just an academic thing? Let me try to give you a clue. Again, I am not a card-carrying member of the cool functional programmer kids club.
[00:13:40] I'm not a real functional programmer. I'm just not. So I have to always come from the outside in. I have to give you my interpretation of what I think it is. And I'm just on this crazy little journey trying to learn stuff and helping you come along that journey with me.
>> Kyle Simpson: But I would hazard that a guess or an assertion that the reason why we don't like side effects is because it makes an individual line of code. For example, line 8, it makes that line impossible to predict out of the context of all the other lines before it.
[00:14:19] Now, that's only on line 8 but imagine that was on line 9,712. There was a function call there. To really, truly understand what line 9,712 was doing, you would have to start at line 1. And compute all of the previous 9,711 lines in your own head before you could then compute line 12 to line 9,712 to understand what it did.
[00:15:12] The computer. But what we have done in this choice, to assign to a variable outside and to use a variable as a side cause. What we've done is essentially forced the reader of our code to do something that they're not naturally good at. To execute the entire program flawlessly, so that they can understand line 9,712.
[00:15:35] And if they don't execute it flawlessly, at every single line, if they make a mistake anywhere, they can't fully trust that they know what that line is gonna do. If you can't fully trust it, why are you even trying to read it? I mean, isn't that the ultimate waste of time to read something and then not have full confidence in what it's gonna do?
[00:16:01] [COUGH] So in essence, our choices here are making the code. While it makes it easier to write, there's no question in my mind that writing side effect code is a lot easier. I'm good at that. Not so great at this functional thing yet, but I'm working on it.
[00:16:20] But I'm great at writing nonfunctional code. It's a lot easier to write it, but why is it that we focus so much on optimizing for our own writing and not optimize much for how people are gonna read it? So, one of the clearest pictures that I can give of the problem with optimizing for writeability instead of readability is that we are forcing that person to do a bunch of extra work just to understand line eight and that's not fair.
[00:16:48] That's not a good idea to require them to do that. How do we use the function definition that we showed on the previous slide? Have we use that version of it? That function can be taken completely an isolation. You can put in an input. Get an output out and know exactly what it's gonna do every single time, it doesn't matter what the rest of the program does.
[00:17:14] Choosing not to program with these side causes and the side effects will allow us to read code much more easily. So, the functional programmer tells us avoid side effects. Avoid side effects wherever possible and I would say, they say that. Because side effects made code a lot harder to read and reason about, and prove.
>> Kyle Simpson: So, let's do a little thought experiment here for just a moment. Cuz I know that we developers love to take something when somebody says something and we're like that sounded cool. We love to take that and extrapolate it to its extreme. So, what's the extreme here? Well, if side effects are bad, that means he's telling me I should not program with any side effects.
[00:18:05] Let's write a program that's completely side effect free, that must be the goal. Let's do a thought experiment and think about that particular program, a program with no side effects. Think about how would you observed that program and what it did if it had no side effects? Well, refer to the console.
[00:18:25] That's a side effect. Input, output is a side effect. A macrolevel obviously at the system level, it's a side effect. Well, it could like write to a file system. That's a side effect. Well, it could add dome element to the page. That's a side effect. It can make a network request.
[00:18:46] That's a side effect. Shoot, I can't really observe this program. If everything that it does on the outside you're telling me that's gonna be a side effect, how could I possibly observe it? Here's what I'm getting out. If you truly did write a program that had no side effects, that program would be indistinguishable from the absence of that program.
[00:19:11] It would be impossible to prove that that program existed, if you wrote that program absolutely with no side effects. It would be impossible to prove it even existed. I'll go so far with this little thought experiment as to say. What about the heat produced by the CPU when it runs said program?
[00:19:34] Is that a side effect? It's definitely something that can be observed, I think it falls under the definition of a side effect. It's not a direct output. It's an indirect output. How about this? What about the delay in time when the CPU is running your program and doesn't run some other program at that exact instant?
[00:19:57] Is that delay in time a side effect? It's an observable behavior of the overall system. It is not possible, only in theory not actually practically possible to write a program with no side effects. Functional programmers know this. Pascal programmers know you have to have side effects. They made a language that has no side effects and they're like we gotta have side effects.
[00:20:24] So, they came up with a trick. It's called the IO monad. Let's them get around some of the rules of the language to do side effects. Now a Haskell programmer says, the best thing for me to do is write as much of my program as I can without side effects and only have a few side effects as minimally as possible.
[00:20:47] That's really what we mean when we say, avoid side effects. What we really mean is avoid them, if at all possible. Anything you do, print to the console. Add something to the DOM. Make a network request. Write to a file. Any of that is gonna be output and you're gonna need to do at least some of that for your program to be useful, but I think the natural state of affairs is that we just sort of strewn our side effects all across our entire code base.
[00:21:18] The functional programmer says, the more you do that, the harder it is to understand the code base and even harder it is to fix bugs when they happen. So the functional programmer says, how about if I make as much of my program without side effects as possible? And then the parts that do have side effects, I'm gonna collect them in a very specific location.
[00:21:40] I'm gonna label them obviously as having these side effects. And then if I have a bug, guess where I'm gonna look first? In the code with side effects, because it's vastly more likely that my bug is coming from a side effect than from coming from my pure usage of mathematically proven concepts.
[00:22:01] Not guaranteed, but it's like 99 with a bunch of 9s after it more likely that it's in the side effect code. Just choosing to organize our code into those two buckets. The bucket of no side effects and the bucket of side effects, drastically improves our ability to read about and reason about and debug our code and I think that's a really important effort on our part that we should expend.
[00:22:28] I think we should try really hard to make it, so that it's obvious where the side effects are. We should avoid them wherever possible. I love how the functional programmer typically will describe this. They'll describe like a sphere. Remember, I love these concrete visualization things. They'll describe a sphere and they'll say, the inside of the sphere, the core, the air inside of a basketball if you're looking for something really concrete.
[00:22:54] The inside of it is pure. It has no side effects and we put the side effects on the outer shell, so they're really easy to find and they're really obvious. I love that visualization and this is not just theory. As a matter of fact, I bet there's a bunch of you that are already using a system that's already implementing this exact idea.
[00:23:22] Those of you in the react ecosystems, specifically those of you that use Redux. You might not have ever stopped to think, because they don't use a lot of the functional programming terminology within that tool. You might not ever stop to think what it's doing, but it's basically saying, here's all these steps.
[00:23:35] These reducers and this flow of data that you go through to compute all the different stuff, but where does the DOM happen? At the very end, you call out to a render function or whatever it is. Instead of rendering all along the way, having side effects in your DOM all along the way, it's a lot easier for us to reason about our program if this part of the program is just data transfer and then have a function at the very end where we render stuff out.
[00:24:11] That is an embodiment for this principle. Avoiding side effects where ever possible. Collecting the one's over here when we need them. Making it obvious. I've gone so far is to literally put a comments above every functions that has side effects that I had to put side effects in.
[00:24:27] I put a comment that says, Darby Dragons, there's side effects here. Cuz I wanna make it as obvious as I possibly can. I even try to collect them into a file called side effects. Do whatever you can to make it obvious where the side effects are and try to avoid the side effects per possible.