Transcript from the "Memoization" Lesson
>> Kyle Simpson: So in both cases, closure enables us. It's a choice of where we put the code to decide whether we want it to happen eagerly now or lazily later. The problem is that that's a tradeoff. It'd be kind of nice if there was a happy medium, some way to have the best of both worlds, right?
[00:00:18] Well, let's think about this. We want to do the work only once but we don't want to do the work unless it's been asked for. So what if we put the work back inside of the inner function, back online three and a half now, but what if we can somehow detect that the work had it been down and do it once but then never do it again?
[00:00:42] That means it's kind of like compromised between the two. So what if we did this? What if we had a string variable line two and then our function that we return, it checks to see if string is undefined, which will only ever be true the first time. And if it is undefined, we do the work.
[00:01:00] And then from then on, we never redo the work because we only did it once and now string is actually a string, it's not undefined anymore. So we just skip over that if statement and just keep returning the same result. Now what line does the work happen on?
[00:01:17] Is it line 11 or line 13?
>> Speaker 2: 13.
>> Kyle Simpson: And what about line 14? It doesn't happen on line 14, right? Cuz we've already done it on line 13. So now we have the best of both worlds, we deferred but then we still only did it once, and we cached the result.
[00:01:36] Now, in the previous examples, it was pretty clear, we are closed our variable that wasn't changing, so the function was still pure. But I wanna ask you, how sure are you that this code is functionally pure?
>> Kyle Simpson: Look at what's happening. Remember, we said one of the key hallmarks is that if we're gonna close over something and it still needs to be functionally pure, what did we say we cannot close over?
>> Kyle Simpson: We said absolutely don't close over something that what? That changes. Does the string variable change over time?
>> Speaker 3: Yes.
>> Kyle Simpson: It changes from undefined to being defined as a string, and we are closed over a thing that is changing. That should be very, very suspicious to you, red flags should be going off saying.
[00:02:34] Is this functionally pure or not? That's what a functional programmer does, they look at something and they say, wait a minute, you closed over a variable that gets reassigned. I'm not sure about this, right? Because, usually, when you close over a variable that gets reassigned, what is usually the case?
[00:02:50] It's usually impure, right? So what about our A function call on line 13? Is it a pure function call?
>> Kyle Simpson: The answer to that question can be found on line 14. And line 15 and 16 and 17. No matter how many times we call it. How many times could I call this function and have it return the exact same string of Ten As?
[00:03:18] No matter how many times I call it, I'm always gonna get back As, right? Remember, when I turned the Rubik's Cube earlier and I said a pure function call is characterized by, given the same input, it always produces the same output. So A satisfies that definition of a pure function call.
[00:03:41] But the code is not obviously pure. We have to go convince ourselves that it is pure. Are you following me? So we've defeated one of the most important reasons for doing functional programming. I mean, yes, we want a program to work, but functional programming is not worth it unless it is readable and understandable.
[00:04:08] If a reader read this code, if they glanced at it immediately they would be much more likely to assume, a functional programmer, would be more likely to assume that this code is impure than to assume that it's pure. And they would have to convince themselves that it was pure.
[00:04:24] So in other words, I would say, this code style leads to a low degree of confidence in the function period, as opposed to what we saw on the previous slide, which we probably would've said high degree of confidence in the purity. Because it's very clear the variable doesn't get reassigned.
[00:04:40] Here it gets reassigned, but it's a very special way of getting reassigned that isn't observable and, therefore, still preserves the magic of function purity. So should you do this? This is certainly better for performance. We got the better trade off, or the better performance benefit, we got lazy, but only did once in cash.
[00:05:05] For performance reasons, you might wanna do this. But for stylistic reasons, have we shot ourselves in the foot?
>> Kyle Simpson: So what if there was a way of achieving this performance benefit by having a declarative style? Well now that would truly sound functional, wouldn't it? I turns out, there is a utility for exactly this kind of purpose.
[00:05:29] Which is, if you have a pure function, which all your functions that you want to talk about in this course, they should all be pure. If you have a pure function, you know that giving the same input is always gonna produce the same output, or at least, It's supposed to if it's pure.
[00:05:45] So what if we had some magic way that we could only ever compute an output for an input once? And then sort of cache that information and every other time that that same set of inputs came in just return the cached output.
>> Kyle Simpson: That'd be pretty cool, wouldn't it?
[00:06:05] It turns out just such a utility exists, and it's called memoize. We built into all of your favorite functional programming libraries. Look at what memoize does. It takes a function that is not optimized. What is line 2 through 4 showing us? A lazy function, right? One that's gonna do the work every time.
[00:06:28] Memoize adapts that function, it produces a new adapted function. It doesn't actually have a different shape this time, it just has a different behavior. In this case, the different behavior is that it's wrapped with some code which you don't see here. It's wrapped with some code that will check all of the inputs, and if those inputs have been passed in before, simply return the cache output.
[00:06:54] So it maintains an internal cache that you don't see. So a couple of takeaways here. Number one, this style of code hopefully is much more obviously pure T. Because we are crossing over any variable that gets re-assigned. All the variables that we are using, they only get assigned once.
[00:07:20] Everybody with me? The count variable, the closed server count, so in other words, the original function that we wrote was the best version of the function we wrote. We didn't need to change its definition. We just needed to memoize it.
>> Kyle Simpson: By the way, this term, memoization, I don't know where that word comes from.
[00:07:41] I've tried to look. I've tried to search for it. I don't know where it comes from, but I have a way of thinking about it that works for me. Memoization is like the word memorization, but they forgot the r.
>> Kyle Simpson: Like 90% of you didn't get that joke, but that's okay.
[00:07:58] Memoization is like the word memorization without the r in it, okay? I don't know if that's where it comes from, but that's what works for me. That's how I memorize and how I remember it, is that it's memorization without the R. So, memoization, that's this technique. One takeaway is that it allows us to write that more declarative style that is more obviously pure.
[00:08:21] But there's another takeaway. Something that's very important for you, because you might think to yourself, seems like this is a pretty cool utility. Maybe I ought to just memorize every single function I've ever write, right? You might be thinking that. Consider the cost of memoization. It's not just that we're wrapping a function around it.
[00:08:45] Consider the cost. Memoization is maintaining an eternal cache, which is going to use additional memory. And there will be some slight additional processing around. So you should ask yourself before passing any function to a memoize utility, you should ask yourself, is this function likely to benefit from memorization?
[00:09:06] So what are the cases where memorization is useful? One characteristic is if you expect it to be called multiple times with the same input. Well then that's clearly a case where you want to use memorization. What if you expected it to be called lots of different times, but every time it was called, it was being called with a different input?
[00:09:31] Would that be a good usage of a memoization?
>> Speaker 3: It would take up a lot of memory.
>> Kyle Simpson: Use up a lot of memory, right? So you have to somehow be able to predict your likely expected use case, your usage pattern, for a function, to decide if it should be memoized.
[00:09:46] Do not just memoize everything because it's a cool trick. Memoize it if the usage pattern dictates that you would benefit from that performance improvement. Otherwise, take out the memoize, go back to two slides ago, and just leave it as a lazy function.
>> Kyle Simpson: You with me?
>> Kyle Simpson: That's the benefit of thinking about this, in this bigger sense, thinking about function purity as what's happening with the function calls.
[00:10:18] Now, you have a question and-
>> Speaker 2: The last example before we do the memoize, is that-
>> Speaker 2: Kind of an impure way of writing memoize?
>> Kyle Simpson: Yep.
>> Speaker 2: I'm just curious how-
>> Kyle Simpson: It's not an impure way of writing memoize, it's just not as obviously pure.
>> Speaker 2: Yeah-
>> Kyle Simpson: It's still technically pure-
>> Speaker 2: I'm just curious-
>> Kyle Simpson: But it's not obviously so. And it's more imperative.
>> Speaker 2: I'm just curious how memoize would be written so that we wouldn't have this necessarily obvious, or is it written this way?
>> Kyle Simpson: It's basically written this way.
>> Speaker 2: But it's just-
>> Kyle Simpson: But it's inside of a utility that's a vetted mathematically proven thing, as opposed to cluttering up our code and making the reader of our code second guess, hey, is this pure enough? We know we can trust memoize is provided to us by a well-known functional library. We don't know if you can trust your ad hoc memoization on the fly.
[00:11:08] You follow me? That's a great question, by the way. That's the key. It's not that these things don't happen, it's where they happen. Do we shift them into this thing and allow that detail to be handled over here, so that my code I can focus on the important stuff.