Check out a free preview of the full Functional-Light JavaScript, v2 course:
The "Purifying Functions" Lesson is part of the full, Functional-Light JavaScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Kyle reviews how to purify a function removing side effects, but the tradeoffs include pragmatic approach.

Get Unlimited Access Now

Transcript from the "Purifying Functions" Lesson

[00:00:00]
>> Kyle Simpson: All right. So our goal is to avoid side effects wherever possible. For example, this function organizes itself with a direct input, the x parameter. And it has a direct output, the return statement. And that means that when we call something online seven, and we pass in the value zero, the output that we get which we can choose to assign to y if we want or not but that output is entirely independent of anything that happens before or after that line.

[00:00:32] So, it takes that line to be a single, individual line of reasoning rather than having to reason about lines 1-6 to get to that point in the program. Now, the takeaway should be avoid side effects wherever possible. But if you have some scenario where you can't actually write the function with no side effects, maybe it's a function you don't control.

[00:01:00] You still have to use it in your system but maybe it's something you don't control, so you're not really allowed to change it. There's a couple of things, a couple of techniques that we can use to minimize the impact of the side effects to essentially hide those side effects, if you will, so that they're not polluting the rest of our code.

[00:01:20] So we wanna talk about how we can get to these pure functions. That's the term for a function, that doesn't have any side effects. These are pure functions, return x and y. Return x + y + z is not pure cuz we're referencing a variable outside of itself.

[00:01:38] So how do we purify a piece of code that's not already pure? What can we do?
>> Kyle Simpson: Well, the problem that we have with this code is that there is a set of inputs that are coming from the lexical scope. The x variable is a variable in that lexical scope that f is able to access.

[00:02:03] So if I wanted to in essence hide or encapsulate that what I could do is create a lexical scope around F that included an X, so when F tried to access an X, it was accessing one that was in this enclosed, this encapsulated scope. So, here's an example of that.

[00:02:23] I made a capital F function, and I have an x and, in this case, also a y. I have the entire universe that the lowercase f on line six would care about. Both input indirect and output indirect. I have that universe contained within the lexical scope of the F function from line one.

[00:02:44] What that means is that when I run that f function on line three it will produce a side effect, but that side effect is contained within the outer F function. So the outer F function then becomes as we see on line 13, an actual pure function from it's observable behavior it has no side effects on the rest of the program.

[00:03:13] Line 13 will always behave exactly that way. If I pass in 0, I will always get the value 3 back out. I can call it over and over again and I'll get the exact same three with the exact same input, so observably the capital F function is pure.

[00:03:29] Even though on the inside it has all this impurity happening, it has all these side causes and side effects happening. Now, I show you this not because it is common that you're going to be able to wrap a function around some other function. The truth is if you have some function in your app, and for whatever reason, you're not allowed to change the definition of the function, you're also not likely to be able to wrap a function around it lexically.

[00:03:56] You're not likely to be able to change anything about the file. Maybe it's a function provided to you by a third party library and you load it dynamically and you don't control it, so you can't change it. So I don't really show you this to say, this is how you're gonna always fix these problems.

[00:04:11] But I do wanna make a different point here, which is the functional programmer knows that the importance of functional purity, that is avoiding the side effects and side causes the importance of that is observational not academic. The importance of that is that line 13 acts as a standalone.

[00:04:30] It doesn't actually matter what happens on the inside of f. As long as the outer perception, the outer observation of that function is that it behaves as pure, then we're golden. So, a principal that you will see. And you'll see this play out many times throughout the rest of this workshop is that it doesn't need to be turtles all the way down.

[00:04:53] When we completely contain all of those side effects, it doesn't matter what happens on the inside. And as a matter of fact, it does matter. You should seek to do it in the most efficient way possible. From the perspective of creating a utility that then is going to be reused.

[00:05:12] But there is clearly a trade off here, because the uglier you make the code inside of f the harder you make f itself to understand and read and verify. So if you go crazy and make it really all kinds of arcane and all kinds of wierd stuff going on, you might very well make the rest of the program easy to understand, because the rest of the program looks like line 13, very verifiable, very provable but the stuff inside of f, not so great.

[00:05:41]
>> Kyle Simpson: So you wanna make those changes only when performance triumphs the readability, and following where I'm coming from. You don't wanna just write ugly side effect code just because it's fun. You want to cheat and use side effects and use any other kinda trick in the book when it's gonna help the performance of the system.

[00:06:01] And I guess I could say the converse of that is, if there's someplace in your system that needs to cheat for performance, then what you should do is encapsulate those cheats, that side effect. In a pure wrapper. If there's something you need to do, like, I need to push into the array instead of creating a new array for each item that I need to add into it, I need to mutate my array, we'll talk about value and mutability a bit later in the course.

[00:06:28] If you need to push something in an array because it's more efficient that way and you are going to do this millions and millions of times that's fine but just encapsulate that in a function so that that is not polluting the rest of the program. That's the goal of avoiding side effects.

[00:06:45] And this is what we're going to see over and over in this course. My functional light pragmatic balance between the academic concept and what we need to do to ship code and ship code that works. I don't hear a lot of functional programmers talking about that balance and that trade off but it's core central to premise that functional light is what helps you start climbing the mountain.

[00:07:13]
>> Kyle Simpson: So one way of fixing this problem was to completely encapsulate all of the possible side effects, so that they can't be observed by the outside part of the program. Where possible, you should probably do it that way. Maybe not gonna be possible all the time. So, another technique, and this one definitely feels a bit gross, I totally understand that.

[00:07:36] But another technique is to create a function that acts as an interface to the function in question. So here, lowercase stuff is not inside of f, but f acts as an interface to that function. You'll notice that f's job, Capital F on line 5, its job is to capture the current state, set to something predictable, run the side effect, capture the output state and then reset everything as if nothing had changed.

[00:08:09]
>> Kyle Simpson: So when we run line 16, during the running of line 16, there are definitely side effects happening in the system. No question. We are changing variables like the x and y from 14. We're definitely changing those as F runs. But at the end of F, everything has been set back to exactly the way it was before.

[00:08:34]
>> Kyle Simpson: So, we go back to asking ourselves observably, is capital F acting as a pure function? Observationally, it is acting as a pure function because it has prevented us from being able to observe or worry about those side effects throughout the rest of our programming. Line 16 now can be understood in isolation without having to run lines 1 through 15 of the code to figure it out.

[00:09:03] There's no question this is gross and there's no question that it would be better to do it a different way. But this is kind of your fall back to I haven't been able to come up with any other way to deal with the problem. But I really want to prevent these side effects from polluting all of my code and making all of my code difficult to understand and I'm going to use capital F in lots of places, so that could impact a lot of code.

[00:09:25] I'm going to go to the trouble to do this. By the way, this is just a couple of variables. If this was dozens of variables, If it involved modifications to the dome, if it involved network requests, fixing that is going to be really challenging. It's not going to be that straight forward for you to do, I get that entirely.

[00:09:48] But, you have to judge for yourself in that pragmatic balance. Is the benefit worth it? Is it worth it to me to got his extra step so that I protect all the usages of that through out the program from colluding. Is there a question in chat here?
>> Speaker 2: Yeah, just that if the first function doesn't have a turn does that mean it's not a function?

[00:10:13]
>> Kyle Simpson: Yeah, we're deliberately saying is the F right here. The one on line one, the lowercase f. That's our running example of bad, impure function. That is our bad, impure function. One of the reasons why it's impure is it doesn't have a return statement. Another reason why it's impure is that it references an x variable outside of itself.

[00:10:40] So we're starting to build up for ourselves a definition for function purity. And, we're actually going to evolve this definition as we go along through the course. And it's not that when we get a new definition it's going to replace it. This is a little bit more like a Rubix Cube.

[00:10:58] And right now, we're going to look at one face of the Rubix Cube. We're going to look at the red side. That's gonna give us a definition, but that doesn't tell the full story of what the Rubik's cube is. So right now, our definition of functional purity, looking only at the red side of the Rubik's cube, is to suggest that a pure function is a function that does not have any access to variables outside of itself that's observable.

[00:11:31]
>> Kyle Simpson: If a function accesses something outside of itself, and we can observe that access and change something about it, or it changes something about the state of the system, it's not pure.