. Alright. In this segment, we're going to start studying case expressions, which is the ML language concept we're going to use to access values made up from the types we introduced with data type bindings. So, what we're going to do is we're going to take these two ideas we learned in the last segment. Then we need a way to figure out which variant of a one of type we have. And we need a way to get the pieces out. We're going to see that ML combines these into a single language construct. In future segments we'll see its actually much more powerful than we're showing here. But we'll introduce the basics to see how you can use it. So you see the example here on the slide. I also have it here in ML. So in the, in a, file here. So you see the same datatype binding we studied before. So this is a new type, MI type, its built from one of these three constructors, either two ints, str, or pizza. Okay And now what I have is a function that's actually going to take in something of type MI type. We could have written this here, but as we'll see this is not op, this is optional. It's fine, to let ML figure this out for us. And this is going to take in a MI type, and in this case, return an int. Alright? So the way we're going to do this is we're going to have this case expression. What we're going to do is between the case and the of, which are both keywords, we're going to have a value that was built out of the data type. In this case, MI type, if we had a different data type binding with use case expressions on that too. We use case expressions to access the pieces of a data type. Then what we have, as indicated by the english word case, is several cases. And each of these cases is separated by this pipe character, and what this basically says is if it's a pizza, then take this branch, so like an if-then-else, it's something with branches, and evaluate this thing on the right. So you'll get three. If it's a string, then I take this branch and get an eight. Well, what is this s for? This s is a variable that we will bind to the data under the string constructor. Now, we're not using that here because we didn't want to use the string, but we could have. And this variable s would be in scope in this branch. And finally in this last branch, we actually are going to use this idea. We're going to say, take this branch if x was made from the two ints constructor. And when evaluating this corresponding expression, let i1 be the first int made from the two ints and i2 be the second int that was provided when we made the two ints. And so in this case we'll add the two branches together. Alright? So let's try that out. Let's go over and use this file. And sure enough f is just a function from type MI Type to int and now we can try calling f with pizza, and we'll get three. Or we can call f with str of hi, and we'll get eight. Notice by the way we can't call f with just hi. Right. Hi is type string, right, not type MI Type. So if we do this we'll get a type error message that this is not the type that f expects. F's argument x has to be a MI Type and you can tell that because of these patterns that we use to match against the value. And now probably the most interesting case if we call f with two ints of some pair of ints like maybe (7,9), we will get sixteen. Alright? So, that's how you use it. Of course, if we had, had some val x that equals a str of hi, then we could have called f with x and this would all work as usual. Okay so now let's go back and look at that case expression a little more carefully, to make sure we understand its syntax and evaluation rules and typing rules. And I think the slides will help us do that. Okay so here's the same code, so we're really focusing in on this case expression and in one sense its a multi-branch conditional. Its like an if-then-else, if-then-else that's nested. What we do is we evaluate this expression between case and of. That's going to give us some value And then we're going to see which branch matches. So this is called pattern matching, because to the left of this arrow, the equal angle bracket, are different patterns. Alright? And we take the first branch that matches. And the definition of matching is built from the same constructor. Alright? Once we build the same, match the constructor we then use these variables that are part of the pattern to introduce local bindings. So in this case, its like a little let expression. Alright? So this is letting i1 being the first int and i2 being the second int in the pair that was made with the two ints. And in this branch we're letting s be the underlying string. And we can choose whatever variable names we want and the scope of the variables is just that branch alright. Okay. So the pattern match [INAUDIBLE], is first, find what branch matches and bind the variables appropriately, then in that extended environment, evaluate the expression on the right and that's the answer for the whole thing. So this example is slightly different than what I showed you in the code, but in each case, we have some expression over here. They all have to have the same type, just like a then branch, and an else branch have to have the same type, because the result of the entire case expression is going to be the result of whatever branch's expression we evaluate. And so the type of the entire case expression has to be the type of those branches. Okay? So that is a case expression. In general, let's now generalize it past our example, we're going to have some expression between the case and the of. And then we're going to have a bunch of branches. Each branch is a pattern, which I'm writing with p here, and then this equal angle bracket and then an expression. And we separate them with these pipe characters. So a pattern is a new kind of thing. I know that they look like expressions. If I go back here two ints i1 comma i2, kind of looks like an expression. It is not an expression. Okay, it is something we're going to use to intro, to do the matching and then to introduce variable bindings. Okay for the corresponding branch. So for today, in this segment, each pattern is just a constructor name, and then the correct number of variables. So for pizza, it was just the constructor name. For str, it was the constructor name and one variable. For two ints, it was the constructor name and then two variables in parenthesis separated by commas. If you had three arguments, it would be similar and so on. So syntactically, they look like expressions, but we don't evaluate them. They're not expressions. We use them for pattern matching. And then what we do is, we match the result of evaluating the zero against those patterns, get the variables bound to the corresponding pieces, and evaluate the same branch on the right-hand side. So, why is this better than a different model where we just provided functions like istr and getstr data like I said ML could have done? Well first of all, if you really wanted such functions you could define them yourself. You could use pattern matching to write a function of type MI type arrow bool that for str return true and for any two ints or pizza would return false. Do not do that. It's poor style. You're giving up most the benefits of pattern matching. But certainly, case expressions are just as powerful as that approach because we can use case expressions to do that approach. Okay. But these next two reasons, one and two, are probably the most important ones. Which is that thanks to writing all your different possibilities together in a case expression. The compiler can check that you don't forget any cases and you don't have any redundant cases. So let me show that by flipping back here. You might of seen, I had some commented out things here. Suppose I un-comment this last case. So now I have two branches for pizza. Alright? Could be an error that you make. If you come over here to SML, always restart by the way, otherwise you end up shadowing the constructors, it's very confusing. case expressions.SML. oh look at that. It no longer compiles, and in fact, it actually says that we have a redundant notch. It's a compiled time error because it knows that the fourth branch in my code could never be taken. Oops, that's a different file. There we go. That this fourth branch could never be taken, and so it's a compile time error since you know, there'd be no reason to write such code. And so that's very helpful. suppose conversely that we forgot our two ints case, or rather than showing you that how about I leave that in and I show you this other function that forgets a couple cases. It forgets both the str'd case and the two ints case. Well in this case I believe we will just get a warning if I recall correctly, but a warning that we would be very wise to heed. and it says, warning match non exhaustive. And indeed, if you take that function G, and you call it with a str of hi, you now get a run time exception saying, I tried to find a matching pattern, and I didn't. And, if we don't have any of those warnings when we compile, then we know that will never happen. So, this is a nice guarantee we're getting, from the type checker, which is looking at all our patterns and making sure we've covered all the possibilities. Okay, so that's the first two reasons. The third reason is that we'll never, if we use case expressions the way they're intended, do things that lead to errors, like head of the empty list. That instead, we always have one case and in that case, the patterns already extracted the values. So when we had, in our program over here, str of s, we already know, in this case, that s is going to be down to the underlying string. And we'll never make the mistake that over here in this branch of trying to get, one of the int values under a two ints, right? Because, we just use the pattern to get the values out. Okay. And finally we're not done showing you case expressions, alright? This is the simple use of them but we're going to generalize them. It turns out patterns can be much richer and more powerful than we've shown so far. And we'll be able to use those patterns to write very elegant and very concise code. Much more elegant and concise than if we had to use functions for checking variants and extracting pieces of data.