, In this segment we're going to begin emphasizing, in our semantics, when expressions get evaluated. This is a key concept in programming languages and it's going to be essential for understanding the programming idioms we're going to see in the next few segments. So for every language construct and every programming language, the semantics does have to specify if or when those sub expressions appear to get evaluated. For two crucial examples, let's focus on function calls and conditionals. So in ML and Racket and other languages you're probably familiar with. When you have a function call, the arguments to the call are evaluated before the call starts, before you evaluate the function body. Each of them is evaluated once, and then the function body uses variables. The function parameters to look up the results of those computations that already occurred. Conditionals work very differently. If you look at an if, that takes, three expressions, the test, the true branch, and the false branch, we do not evaluate all of them. Eagerly like that. We evaluate just the first one and then use the result to decide which of the other two we evaluate and then the other one is never evaluated. So we have not emphasized this quite as much before, but this piece of the semantics of language constrcuts is essential for programming correctly, so I'm going to switch over to the code, which I have written out here, and show you a number of examples that show that. So let's start with an ordinary function, a totally normal implementation of factorial, we've seen such an example many times. And if I click run and use it, it will work as expected, normal of 5, I'll get 120. And by the way this function grows very fast and Racket has no problem with large numbers. So if I ask factorial 500 It's happy to print out this very large number that I will trust is 500 times 499 times 498 and so on, and everything works correctly. And what happened is when I called factorial normal of 500, if I had it evaluated 500. That w as a value. Went into the body, decided oh, x is 0, so I will evaluate the false branch because x is not 0, excuse me. So only because 500 is not 0, do we decide to do this multiplication. And when we do a multiplication, we eagerly look up x. We get 500. And then, we do this recursive call, which after a whole lot of work returns a very large number. And then we do a multiplication And then we return. So why am I emphasizing this? Let me show you a slight variation. Suppose I try to write a little wrapper for if. So I have a function, my-if-bad, it takes three functions, three arguments, e1, e2 and e3. And then it just calls if with e1, e2, and e3. So it looks like it's just an unnecessary function wrapping of if. And here's a version of factorial-bad that uses my if-bad. Now I encourage you to try this out on your own. I'm not going to do it while we're recording, but if you call factorial-bad with any number whatsoever It will never terminate. It will never terminate. The reason is you call factorial-bad with an argument, 0, 500, 5, I don't care what it is, negative 7, and the 1st thing it has to do is call the my-if-bad function. And before you call a function, you evaluate all three arguments. So you see if x is 0. That will go quickly. You see if 1, you evaluate 1. That goes quickly. But then, you have to evaluate this as well. And that will do a recursive call, and that will do a recursive call, and we will never end the recursion. See it's essential to all of our recursive functions that if, as we saw up here in the good version, does not evaluate both of its branches. It only evaluates the one we need. But my-if-bad being a function evaluates both. So it turns out that I'm going to make a third version of the example, which is actually going to work. This is not good style. The first version is good style, but it's going to introduce an idiom that's going to prove very useful to us in upcoming segments. And that is if you really want a function It takes three arguments a nd acts like if. You have to change what it takes for some of those arguments. Have it take for e2 and e3 not the result you want, but a zero argument function that if you call that function. You get back the result you want. So what my-if-strange-but-works does is it evaluates e1 to get an answer, and then it either calls e2 with no arguments, or calls e3 with no arguments, because remember in Racket, if you want to call. A zero argument function that's, in some expression, e, you write e inside a parentheses. So e to an e3 and any call my-if-strange-but-works have to themselves be zero argument functions. So we're doing higher order functions here. And now, if you want to write factorial using my-if-strange-but-works, you can do that. Let me just make this on the screen for you. So factorial okay takes in a number x. Says if x, is z- it calls my-if-strange-but-works with three arguments. The result of is x 0. This function, and this function. So, functions do not evaluate their bodies until you call them. We know that. So all three of these arguments. Evaluate very quickly. If x is zero, that's very fast. These other two are already values. We just make the right closure, and then we call my-if-strange-but-works, and my-if-strange-but-works decides which one of these lambdas to call. And it never calls the other one. And if you never call a function, its body is never going to execute. So this works just fine. If I say factorial-okay of 500. I will get my very big number, 'kay? So that is the code I wanted to show you. Let me emphasize yet again what is going on and what we call these things. There's some terminology I want to introduce related to this idiom. Okay, so we know how to delay evaluation. If you have an expression that you don't want to evaluate yet, you want it to evaluate later or maybe not at all, put it in a function and don't call the function. Thanks to closures, you can really do this anywhere. Anywhere you want, you can just take some expression e, replace it with lambda, take no arguments of e And now you have a procedure that when you call it, will produce the answer of then. But you do have to remember to call it. So that's what we saw here. You see it on the side, compared to the example that did not work, our definition of my if function had to remember to either Call its 2nd argument or call its 3rd argument and then our use of it in factorial had to not pass in the expressions that we wanted to evaluate, but to pass in 0 argument functions that we could then call to get the answers we wanted. So we have names for this. so let me, actually let me go back on this slide here. I forgot to say this middle part I wanted to say. So when you have these 0-argument functions, we actually have a name for them. They're called thunks. YOu can read on the Internet where people think this word came from. Nobody is quite sure. Or at least I have not heard a definitive account, but it's just a funny word that computer scientists use. A zero-argument function that is used for the purpose of delaying evaluation in this way is called a thunk. You can even use this as a verb. If you want to tell someone, No, no, no. YOu're evaluating that thing too eagerly" We might not need it passing a function that produces the result. You can just say, oh, thunk that. Thunk the expression or the function needs a thunk. You can say all of these things, okay? So let's emphasize, 3 very different things in racket. They look similar, but it's essential we understand the difference, or you're going to find it very difficult to do your next homework assignment. When you have some expression e, we evaluate it and we get a result. So maybe e is plus 3, 4. We evaluate that, we get 7. But if instead we have a thunk with e in it, this is a function. That will not evaluate e at all until you call it. And then every time you call this thunk, we will evaluate e and get the result. So lambda no arguments plus 3 4 is a procedure. That when you call it, you get 7. And how do you c all a thunk? It looks strange for people not used to parentheses, but if you put e in parentheses, we evaluate e. To get a thunk, and then we call the thunk that evaluates the thunk's body, and that is our final result. We've seen this. It's why if we put 37 in parentheses we get an error. 'Cause 37 is not a thunk, and we put something in parentheses You're calling it with 0 arguments but if have lambda (, ) 37, then we could call it as you see here with parenthesis around it and you would get back to 37. So we haven't done this in a useful way yet, the if that's built into Racket is the right thing to use. But the upcoming segments are going to use thunks to code up some very powerful things in racket. to use the idea that often delaying or avoiding a computation is exactly what you want to do.