TypeScript Fundamentals, v3 Union Types
Transcript from the "Union Types" Lesson
>> The next topic we're going to talk about today is union and intersection types. And these conceptually can be thought of as logical Boolean operators, and by that we mean, and, or. There are other operators that have no representation in typescript but we do have this concept of and, and or.
[00:00:24] So this is a hard concept for people to wrap their heads around sometimes. So we're just gonna use a very kind of explicit example here. Let's imagine that we have two sets of things. We have things that are fruits, and then we have things that are sour, and there is some overlap those fruits that are sour.
[00:00:47] So a union type, you can read up on the sort of set theory definition of what a union type is, if you wish, but you don't need to know that in order to be successful with that trip. You can think of a union type as OR, for types.
[00:01:07] And by union, we mean it could be something that is in either of the two circles, including the overlap between them. So if you think of a union like in terms of this logical Boolean operator, I'll show you go to the wiki page. Just to show you this one diagram here, right, it's everything.
[00:01:30] Everything that could be in either category. So that's the OR. Intersection types are only the intersection and that's the end, meaning, the only things that are allowed are things that are in the left side and the right side. That is an intersection type and, so in that case we would be only looking at, lemon, lime and grapefruit and that's because they are both fruits and sour.
[00:02:05] So let's look at how these two concepts are represented in the TypeScript programming language. Union types can be described using this pipe operator. For example, if we had a string, maybe it's like a property on some response object that a function returns and it could either be success literally that one string, or it could be the string error.
[00:02:30] So it's not just any string, it's one of these two specific strings. Here's another example. Let's flip a coin, do a math.random. If it's more than 0.5, let's call that heads otherwise we return tail, so it could be one of these two. And the outcome, you can see it's heads or tails, one or the other.
[00:02:52] And in this case, by the way, note that there's no possibility of it being both heads and tails at the same time. So a union type doesn't have to involve a potential overlapping area here. It could literally be a string or number and there is nothing that is both a string and a number at the same time.
[00:03:15] Sort of two separate circles, but a union type would represent anything that is in either circle, regardless of whether there's an overlap or not. So let's make this a bit more interesting using the tuples concept that we talked about earlier. And this is actually a common pattern that people like to use whenever there may be collecting errors and returning them instead of throwing an error and sort of interrupting the execution of the program.
[00:03:44] So let's say that we are trying to get some user information and that's what we're returning here, is a name and an email address. And we're wrapping that in a tuple, right? So the name and the email address, that's an object and we're storing that object in the second position of the tuple and the first position we're calling success, right?
[00:04:11] See that string success to the extreme right of this tool tip. So that's like our success tuple, it starts with the word success, and then it gives us some data. And then in case of an error, we've got a string error and then an error object instead of throwing it, we're just gonna return it here.
[00:04:27] So what that lets us do, Is we have this thing, right? It could be either option, we can choose how to handle it and we will know what's in the second position of the tuple based on the string that we see in the first position. So we're gonna look at how we can deal with this value now, right?
[00:04:50] It's either this, or it's that, or this piece here, one or the other. So here's how we might consume that value. First, I want you to see that if we look at either element of this tuple by itself, this union type concept has sort of propagated through each member of the tuple, right?
[00:05:21] Our first element here, it's one of these two strings, and then our second element is either an error or this object. So each by themselves, they sort of have this duality until we straighten out what's going on. And I'm going to open this up in the TypeScript playground really quick, because part of the motivating use case here, we can get rid of this output here.
[00:05:45] But part of what's interesting is, why would we need to narrow things down? Why can't we handle this value directly? So if I look at this first thing, right, it's either error or success. And I have a lot of stuff on here that indicates that yeah, this is gonna be a string.
[00:06:06] It's one specific string or another specific string, but definitely a string. This second value down here is a bit more complicated, right? It's an error or this special object and it turns out that all we can access on this is name, why is that? Because error has a property called name, and this object has a property called name, and in both cases, they are strings.
[00:06:31] So all that we're able to access here is the guaranteed behavior, the guaranteed stuff that will be there, regardless of whether it's an error, or whether it's this object with a name and email property. We have a name in either case, but we're very limited in terms of what we can do as long as it's sort of in this state where we're not sure what it is.
[00:06:54] So to access email, for example, I'm not gonna be able to do that, why? Because email does not exist on type error, right? We have to arrive at a conclusion that this is not the error case in order to safely access email, and that motivates what we're about to talk about now.
[00:07:14] So here's the autocomplete we would get for the letter S, like clearly we access sub string behavior. Here we can only access name as we saw. And the bottom line is, when a value has a type that includes a union, we're only able to use this common behavior that's guaranteed to be there, no matter which circle we're in our Venn diagram or two separate circle in our case.
[00:07:44] So the way we address this is through a process called narrowing. And we've already seen this one, so we didn't give it a name but it's this process of using some condition with control flow. So we define a branch of code that will only be taken if we're in our success case and another that will only be taken if we're in our error case.
[00:08:10] And this will be done via this concept called type guards. So later in the course we're going to define our own type guards. These are called user defined type guards. And I like to think of these as where build time validation and runtime behavior meet. We saw one for example that was using type of.
[00:08:36] Here, remember when we were talking about our charging voltage. It was this optional property, it could be there it could not be there. So this is a type guard, it connects build time validation with runtime behavior in that, at runtime, we know that we will only enter this branch of code if charge voltage is defined.
[00:08:58] At build time, we can operate with the assumption that this is defined, right? So it it does serve as glue between these two worlds, runtime check that happens with a compile time narrowing down an increase in specificity of the type. So here in this case, is how we could make use of a type guard.
[00:09:24] So this same function, maybe get user info has two types of things that might return. And we can see that still each element of the tuple has this, maybe could be this, maybe could be that, which is kind of what a union type is all about. And we can narrow things down, we can use instance of, for example.
[00:09:50] So if second is an error, well, this branch of code will only enter it for the arrow case. And then in the else clause of this condition, we get everything that's leftover. After the if is handled, you can kinda think of it like slicing up a pie, where there are two things that this could be.
[00:10:12] And will handle the portion of possible futures where this is an error, and then all that's left is everything that's not error and that's what ends up happening in the else clause, right? So there's still a benefit to being in this else thing. So that's great, it's even better though.
[00:10:31] And this narrowing up here, this would work with just the return object. It's either an error or it's this object, but we can talk about how this tuple gives us some benefit via this concept called discriminated unions. And what this is telling us here, well, let's first look at what we're dealing with.
[00:10:53] We're looking at the first element of the tuple, we're seeing if it exactly matches our specific string. And then look, in this case, the entire tuple is in the error case, right? That's the string error and then the error object and then down here we have our success tuple.
[00:11:13] So TypeScript behind the scenes, clearly understands that if this string is found in the first position of the tuple, we know what's going to be in the second position. It understands that these are not two shifting values either of which could be one or the other, there's one scenario, and then there's another scenario.
[00:11:35] We could never end up with this object here accompanied by the error string. It's clear that TypeScript understands that because our check is on the first part of the tuple and what we're seeing as a benefit is the whole tuple is either in one category or the other.
[00:11:51] This is what's called a discriminated union. And what makes it a discriminated union is we have a convenient key of some sort to use in combination with the type guard that lets us in a broader sense switch between many different possibilities. And this could be a property on an object, or it could be a value in a tuple but usually it's gonna be something where you can have a type guard that looks like this where you can handle one of a number of cases.
[00:12:22] If there were 57 different possible tuples we could return as long as each had a specific key like this. A specific string as the zeroeth element. You could make a nice case switch that would handle each one of those cases, and TypeScript would cooperate with you. And you'd get that nice well formed whole tuple with both the sort of label for it and that second position which is where the deeper information is.
[00:12:54] Another word for this by the way, just in case you see it in the wild, it's a tagged union type, and the tag is error or success. It's whatever that word is, which is the indicator of what should be found in the other position.