, 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.