[MUSIC] In this segment I want to have our first discussion about, how Racket is a dynamically typed language. And emphasize how it let's us build up our own data structures, without any type system, or type checker getting in our way. So this is a major topic that we will discuss later after we're more familiar with dynamic typing. I want to contrast it with static typing and discuss the advantages and disadvantages of each. But for right now, we just need to get used to the idea that racket doesn't detect a lot of things as type errors, and understand what that let's us do. Now, you may get frustrated with the lack of a type checker, as much as you don't like type error messages, they're often better than not getting them. So if you make a mistake in your Racket code, like write something like, n times x, instead of, times nx, Racket will let you run that code without any problem. And if you don't execute that particular expression, you'll never see that it's an error. So it's only if have a test case that gets there, looks up and in the environment, presumably it does not find a function expecting two arguments. The first of which is times, that you'll get an error message, okay? So it makes testing for this sort of error actually more important. But the advantages, it let's us build very flexible data structures without having to go through a bunch of work to tell the type checker what we're trying to do. So the example we're going to do in this segment is, we're going to end up writing a sum function that sums up all the numbers in a list. But it's not just a list of numbers, our list can either have numbers, or other lists of numbers, or inside those lists, more lists that have more lists or numbers. And so what the idea is, to allow arbitrary nesting of lists and numbers as deep as we want, and still sum up all the numbers that appear anywhere in any of those lists. So you can't do this in ML without creating a data type binding and constructors in order to put numbers and lists in the same list. You can't do that according to the type checker that's why you have to use a data type binding, but in Racket, there is no type checker so we'll just be able to do this. So, let's just go over here and explain what I'm talking about, let me first define a couple sample lists so you know this sort of thing I'm doing. My first list here, xs, I can just have a normal list of numbers, four, five, six, okay? I could also have a list say had another list in it with 4 and 5. Then had 6 and 7, then had a list 8. And then maybe 9 and 2 and 3. And then list 0 and 1. So what I've just defined and bound to Y is a list with a bunch of elements. Here's the first one, second one is 6, third is 7, fourth is the list 8, then a 9, 2 and 3. And then this list with more elements, and this list has 4 and 5, and this list has 0 and 1. But I can decimal more deeply. Maybe here I can have a list of 5 and another 0. So, I can mess these things as deep as I want. And it's still going to be the sort of thing I want my function to work properly on, okay? So now let's actually define it, I'm going to define two versions in this segment. So we'll start with sum1, and I'll call the other one sum2, and I forgot a parenthesis here. And I want to take in a list, let's call it X's, this is just shadowing, this is just a function parameter, it's not the list X's I defined above. And what I want to do is, if this argument X's is null then zero right, the sum of the empty list is zero just like we're familiar with, otherwise I can't just go and add the car of X's, because the car might hold another list. So I better not just do that, I better check whether it's a list or a number. I can do that with this number function for all the built in types and racket, they all come with these functions that test at run time. Do you have one of these things? And statically type language, you wouldnt need this the type check would tell you what type of thing you have wear, but in locket we can pass anything, anywhere and so this functions are very useful, so if that variable to true, then we definitely have a number. So let's go ahead and add the car of xs to recursively summing the cdr of xs. So add those two things together, and that's a good true branch for our conditional. Otherwise, we'll assume that if it's not a number it must be a list. Because the sort of thing someone is supposed to work on Is this combination of lists and numbers that go arbitrarily deep. So let's do (sum1 (car xs)). So that is going to sum up everything in the car. The car is itself a list so recursively sum all of those. And then let's recursively sum the cdr Of xs. Add those two things together, that's a good false branch, that takes care of the false branch if we dont have the empty list and that takes of right definition. So let's try this out, you can see the sum function up there and we can try sum1 on xs, and it works fine, we get 15, we can try sum1 on ys, And we get 45. And I'll hope that that's correct, that that sums up all the numbers we have up in that long list. And let's try one more, let's try list of list of list of four, and then of five, and a list of a seven and a two, and that works fine. We get 18 which looks right. But won't work is if maybe deep inside one of these lists, we have something that's not a number or a list. Then, we'll get an error because eventually we will recur to that position and our function assume that we would never hit a string. So something wasn't a number, so we tried to treat it as a list. We tried to take the car of a string And if you take car of hi that does not make any sense, and you have to run time error, okay. So this is fine, someone misused our function. When you misuse a function in a dynamically typed language you often get these sort of errors. But, if we wanted to support this we could write a second version, that if you hit something in a list that is not a number and not a nested list, let's just skip it. Treat it as zero, just skip over it in our summing. Now there's probably a number of ways to do this. I am going to show you one. That is as much like our first version as possible. So, if I add the empty list 0 is a good answer. And if I have a number in the car of the list then I definitely want to add that number to recursively summing everything in the cdr. But now let's not assume I have a list, all right? This is a matter of style, whether it's up to you to force callers to do the right thing, and there will be an error otherwise. Or if you want to say, all right, well if it's a list, then I'll do what I did before, (sum2 (car xs)), and (sum2 (cdr xs))), right? So I have a list in the first position, so recursively add all that, the coder is always another list. And so add all that, and then add the two together, otherwise, I don't want to touch the thing in the car, because it might be something like a string. So let's just sum the elements in the coder of the list, okay? So that's the case we didn't have before, we were assuming we had a list down here, and now we're explicitly checking. I can run this, if you just do alt-p in DrRacket, you get your previous commands, you can cycle through those. And now, this was the thing that gave us an error before, but now instead of calling sum one, I just go back and edit it to be sum two. Then I get the right answer, all right. That 14 it skipped over it, and if this five were instead something like minus three. Sorry, I want something that's not a number or a list. How about false? Okay. Then it'll just nicely skip it and I'll get 13. Now sum2 doesn't work for just anything. It still assumes the initial argument is a list. If I try to call it with hi, I would still get an error message and that's because right here I start checking the car before I even know it has a list, and that's a third version. If you really wanted it to work for anything, and maybe for something else like this return zero, then I'd leave that as an exercise for you. But what we've seen here is that, we could do correct versions of some, that worked for lists of numbers and numbers of lists, without having to create ML-style data type bindings. There's no type declaration at all, we can just, flipping back here to the file, define Xs and Ys to be lists holding anything we want. In fact you can have a single list, hold the false and hi and 14. And racket has no problem with this whatsoever, we can run this, we can ask for the car of zs we get false. We can ask for the cdr of zs, we get the list holding hi and 14 and so on. And so this is more convenient for us, but it also makes it a little harder to test our functions, and to document what type of argument they expect.