TypeScript 3 Fundamentals, v2 Bottom Types
This course has been updated! We now recommend you take the TypeScript Fundamentals, v3 course.
Transcript from the "Bottom Types" Lesson
>> Mike North: So those are top types that can hold any values. Let's look at bottom types. So here's a never. Never, and all bottom types, can hold no values. And a common place you'll see this is narrowing exhaustively. So we've got a value x and we're regarding it as a string or a number.
[00:00:26] I just have to cast here because the compiler's too clever, even if I tried to make it a string or a number, it would say, that's a string. You've initialized it. But that's all I'm trying to get here. So I have one condition here for the string case, one condition here for the number case.
[00:00:46] And you can see here, x is a string. Here, x is a number and down here, x is a never. There's nothing left. This is TypeScript telling you that if you've been good about your types, you can never get here.
>> Mike North: So that's where you might encounter one.
[00:01:06] You're not going to be creating values of type never, but this is where you'll see one. So this is the same example but we're gonna see if we can use never to our favor. And for this, I'm gonna create a new type of error, I wanna a subclass error.
[00:01:24] And will call this UnreachableError. And it's gonna take a never as an argument. The only thing that can type check against that never argument is a value of type never. That's what it means to be a bottom type. A string won't match it, a number won't match it.
[00:01:44] And then of course, we take a message and we kind of pass it along after doing some string interpolation. So, same example as we had above. Except I've just changed this to a number. It doesn't really affect anything. So we've got our string case, our number case. Down here, y is never.
[00:02:04] Here's where this starts to get cool. If I add to this variable the possibility that it could be a boolean. We get a type error down here. So, in creating this subclass of error and in trying to take y and ensure that it is a never down at that last else clause, I am effectively asserting that all of the conditions, all of the possibilities are already taken care of.
[00:02:38] Why should be a never by this point? So this would alert me if I had a big intersection type like this. And I start adding possibilities to it, adding new things that this could be. This at compile time is telling me, now you have unhandled cases. You've gotta go in and add something like this.
>> Mike North: Ooh, and now we're okay again. Because now, down here, y is back to never again. This is what we call an exhaustive switch or an exhaustive conditional, and it works equally well with if else and with case switch. It ensures that we handle everything before we reach that last clause.
[00:03:31] And in a case switch, you would put this in your default clause, right? This is another thing that is in almost every TypeScript app that I write, UnreachableError. That, and IsDefined. If I was in a desert island, and you gave me only a couple TypeScript functions, these would be high on the list, yes.
>> Student: So wouldn't it just not compile so it would never get into UnreachableError because why is-
>> Mike North: Excellent question.
>> Student: How do you get into UnreachableError?
>> Mike North: Excellent, excellent question. There are a couple of ways, one, you could have done some casting. You could have weakened your types in such a way that they're lying to you, and you could end up there.
[00:04:15] Just because TypeScript at that point of code says, this is an array of strings. Shenanigans that were done elsewhere in your program can make it so that's not really an array of strings. So that's one way. Another way, especially if you are writing a library, you'll have consumers that are not necessarily using your type information.
[00:04:35] So, if I had this in a function and I was taking y as an argument, there's nothing stopping someone from passing a boolean in, or an array, or whatever. Remember, TypeScript is a compile time only thing. And it's not doing these checks at run-time. So by the time you make your built version of something and start going, all of that checking is not present anymore.
[00:05:05] That took me a while, to be honest, when I first started using TypeScript to able to delineate between, is this a run-time type-checking thing or a compile time type-checking thing? Another one is HTTP responses. I'd love to be able to type check those as they come in but TypeScript is not there anymore.
[00:05:25] So, no matter what you do, there is the possibility of that stuff leaking in. Which is why when we look at these type guards or like, IsDefined, which we were looking at before. We want to make sure that the implementation of that function is the run-time equivalent of the type guard that we wish to create.
[00:05:50] Right, that type of x is not undefined, that lines up very nicely. But you could have a type guard that's not quite right. And that would very easily allow you to reach this. And that's exactly where you want an exception to be thrown so that you can go and address that and track down how you could have gotten there.
>> Student2: Right, so it's kind of like in the environment of my TypeScript compilation, this is never gonna happen. But if we bundle it up, throw it out there into the wild, it could. So that's the case where we're going to throw the error?
>> Mike North: You're on the right path.
[00:06:25] I would only soften it, TypeScript is telling you, it's never gonna happen, as long as you've been totally honest about your types. The type information for all libraries you depend on is perfect. Your type guards are implemented in a totally correct way. And some of these are really hard to get 100% check marks on.
[00:06:48] But let's compare it to an alternative, and let me remove this boolean. Let's compare it to something else. If we just intended to handle strings and numbers, we could have just done this. But you're never going to find out about these little incongruities, and you want to know that they are there so you can go and address them.
[00:07:34] Because here, it doesn't matter, I could add whatever I want. No clues, no clues anywhere. This is gonna fall straight through. So it's nice to have an exhaustive switch. And this is for languages that feature a high amount of safety, like Rust. They have exhaustive switches and exhaustive conditionals built right into the programming language.