[MUSIC] In this segment I'm finally going to start the discussion of which is better, static versus dynamic typing? And sorry to disappoint you, but I'm not going to reach a firm conclusion. Instead, the way we're going to do this is we're going to consider arguments, reasons, why static checking may be superior or why dynamic checking may be superior. And we will give corresponding similar arguments for both sides on similar topics. We'll start in this segment and then we'll continue in the next one. Remember, of course, that most languages do check some things statically and other things dynamically. So it's not a matter of should you check everything statically or everything dynamically. But for anything you're going to check, there are advantages and disadvantages about when you do the checking. So let's start. Probably one of the most common arguments people make in favor of dynamic type systems, that languages that check things dynamically, is that they're more convenient. That if you want to build a list that holds data of different types, or you want to write a function that returns things of different types, you can just do that. And you don't have a type system getting in your way. So you see the Racket code here at the top. And I have this function f that sometimes returns a number by adding y and y. And sometimes returns the string hi. And if you want to do that, you can just do that. The corresponding ML code at the bottom makes you go and create some data type definition. Introduce your own tags for int and string, rather than the language just doing that for you. And then in the definition of f, you have to remember to put the right constructor in the right place. So it's just clumsier code, it was more work for the programmer. The people in favor of Racket would also point out that when some user of this function f needs to use the result. It has nice built in primitives like number?, that just can therefore know whether the function returned a number or a string. And this is exactly the natural thing to do, knowing that f sometimes returns a number and sometimes returns a string. Whereas over at ML, the program has to use this data type binding we define and then use pattern matching to get the pieces out, okay? So dynamic typing seems more convenient for this sort of program. But the static type system people say, now wait a minute, wait a minute. If you want to talk about convenience, what could be more convenient than have a type system that enforces that the arguments and the data you receive from somebody already has the right type. Suppose I want to write some simple function that just cubes its argument. Well, in ML if I write fun cube = x * x * x, I know absolutely that anyone in the program that every calls cube in anyway, will pass an integer. So I don't have to worry about any error checking, I can just do the multiplication and return the result. Whereas in Racket, we know that you can call a function with any argument you want. So maybe if I want to be more careful, if this is something the outside world might use. I need to put some sort of check here that I was actually passed a number. Racket actually has an entire contract system. I didn't show you here for a more uniform, easy to document, more standard way of doing this sort of error checking. But how it can be convenient, not only that I have to do these checks, but I have to wait until runtime for these checks to fail if someone does actually misuse my function. So that's the argument on that side. Let's give another argument for dynamic typing now. And that's that all that static type checking always prevents useful programs. There's always some program someone wants to write that's been rejected, even though it makes perfect sense. So I have a short example here that I kind of like, just because you might think that ML would allow this, but it doesn't. All right, so let me show you this in ML real quick, not there, just at the here. Suppose I want to write a function that takes in, it's called f, but it takes in another function g, and then it makes a pair of calling g with an int, and g with a bool. It just doesn't type check. Type inference doesn't work that way. The type system doesn't work that way. Even though it would make perfect sense, as long as g was itself some function, maybe just an anonymous function. If I could pass in something that, say, makes a pair of its arguments, this would work perfectly fine. And ML just rejects it. So that's what I have down here at the bottom. You would think this code makes perfect sense and it will not type check. Type system is just not powerful enough for it. The Racket code works just fine. You can define a function f, takes in g. It calls, it makes a pair out of calling g with 7 and g with true. It's exactly the same as the ML code here. And then I can call it. I can call f with a function like (lambda (x) (cons x x) ))). And if I do that, I will get a perfectly good result. I'll get back the pair of 7 and 7, cons together with the pair of true and true. There's nothing wrong with that and so static type checking is just getting in my way. Now, the static checking people would say, now, wait a minute. The only way Racket was able to do that was by tagging all the data in your language and always checking before every operation that you have a pair, for every addition that you have a number, and so on. We can do this in ML. It you want to pass things of different types around, then you just have to use your own data type bindings. And you can now have the control, as the programmer, in tagging exactly where you want to tag, and not tagging where you don't need to. And in the extreme, as I showed already once earlier, you can program in entirely Racket-like way in ML, if you want to. By creating TheOneRacketType with a constructor for every kind of data and do all your own tagging and pattern matching every time you need to use a value. Now, this may be less convenient than in Racket but Racket, in some sense, forces all programs to always have in check tags. Whereas in a statically typed language like ML, the programmer is in control of where things are tagged, where things can fail, where things are checked dynamically. So one more argument in this segment. Probably the best argument people usually make in terms of static checking, is that it catches bugs earlier. What could be better than catching bugs before you ever write test programs, before someone misuses your function? As soon as you try to use the file, you get an error. So let me show that by actually flipping over here to SML again. You see I have here this function pow1. We'll get to pow2 in just a minute. And if I go to try to use this, I get an error message. And the reason is that pow1 want to actually expects to curried arguments, the recursive call tries to pass them in terms of a pair. And it's good that I have an automatic system to catch that sort of bug. Whereas if I'm over here in Racket, I have the corresponding code here, pow1, it takes in one argument and returns a function, so this really is carrying. But then here in the recursive call, I guess I'm passing two arguments instead of passing them in a curried way. And that compiled just fine, and it's not til I try to go and use pow1 with say, 3 and 4, that I get an error. Or even if I get it right here, at the initial call, I'll get an error at the recursive call side. So I have to test it to catch these errors, and figure it out that way. So that is basically the argument in favor of static checking. That it catches these simple bugs for you, that are actually quite hard to find just by staring at the code. But the dynamic type people say, now, wait a minute, wait a minute. Yes, it catches lots of those simple bugs for you. But it tends to catch the easy bugs, right? It catches the same bugs, the dynamic typing people would say, as you were going to catch later when you test your function. And don't even try to tell me that static typing is so good you don't have to test your function. Because that's just not the case. The dynamic typing people would say, okay, ML, I appreciate that your pow1 didn't type check. So let's comment that out, you caught that bug nice and easily. But now, let's look at pow2. So let's go back over here, To SML, and let's use the file now. And look at that, everything compiled. I have a wonderful pow2, takes two curried arguments. And if I call with 3 and 4, I get 13 which is not 3 to the 4th power which is what I was trying to write. And the reason is, pardon me. The reason is that right here I wrote + instead of times. And no type system is ever going to catch that because plus and times take arguments of the same type. And if you're going to have to test your functions anyway, then what's so bad about the Racket code, where testing the code caught my error for me? And I do have here the version of pow2 that is wrong in the same way as the ML version, + instead of times. And I can just test it in the same way, and if I call pow2 in curried form with 3 and 4, I will also get 13, which I should realize is the wrong answer. So those are the first three arguments for and against static and dynamic typing. I believe they're all valid, it's a matter of which is more important to you. And we'll continue with some more reasons in the next segment.