This blog post will look at Union and Intersection types in TypeScript. It is part of the Introduction to Advance Types in TypeScript series.

Union and Intersection types are useful features of TypeScript. But they can come across as a bit strange, especially to developers coming from more traditional languages like Java, C#, etc. Hence in this post, I will first start with a background section. This will help in developing the fundamental perspective needed to understand and appreciate union and intersection types in TypeScript.

### Background: A bit of Set Theory

The important thing is to see type systems from the perspective of Set Theory.

What does this mean?

I’ll explain.

First off, no need to be put off with the mention of set theory. Even though set theory itself is a deep and fundamental topic in mathematics, only the level of set theory learned in high school is required to follow along.

So how does set theory help in understanding types?

In set theory, we have the idea of a set and the elements in the set. A set usually outlines constraints that must be placed on an item before that item can be seen as an element of that set. For example, we can see a fruit basket as a set and the individual fruits in the basket as the element of the set. The constraint being that for an item to be in the basket, it needs to be a fruit.

Mathematicians have a couple of nation for representing sets, one of which is the *Set-builder notation*. Using the set-builder notation, a set is specified as a selection from a larger set, together with a predicate that specifies the condition an element selected from this larger set, must satisfy to be a member of the set being defined.

An example of such notation is shown below:

{x ∈ M : P(x)}.

This can be interpreted as a Set M, which contains element x, as long as x passes the constraints specified by predicate P.

Expressing our fruits basket in this notation may look like this:

{item ∈ Fruit Basket : As long as item is a fruit}

We also have the idea of *set operations* in set theory, these are operations that can be performed on, and with sets.

These include operations like finding the number of elements in a set, compliments of a set, union, and intersection operations, etc.

The idea here is to see a Type as a set, and a value as an element in that set.

So instead of just seeing primitive types like string, number, boolean as just facts of life in statically typed languages, the idea is to see them as delineating a set, while their values are the elements contained in that set.

This means seeing any string value as an element in the string set. The same goes for numbers and booleans. The interesting difference between the boolean type when seen as a set is that the element it can contain is finite.

The value of a boolean can either be true or false, hence the boolean type, which we are seeing as a set, only contains 2 elements: the true and false values.

Using the set notation we can write out the boolean set as follows:

{x ∈ boolean: x == true || x == false }

What about types that are created via the definition of custom types? Can we apply the set theory idea also to those? Yes, we can.

For example, given we have the following class Person definition:

```
Class Person {
private name: string;
private age: number;
constructor(givenName: string, givenAge: number) {
this.name = givenName;
this.age = givenAge;
}
}
```

Traditionally, most developers will see this just as a blueprint for creating objects. ```
let joe: Person = new Person("Joe", 18);
let mary: Person = new Person("Mary", 20);
etc
```

Here *joe*and

*mary*and whatever other objects we create are elements of the Person set.

`{x ∈ Person: x instanceOf Person }`

`{x ∈ Person: x.name exist with type string && x.age exist with type number }`

So for example if we try to assign the value true to a variable we typed as Person, as shown below:

`let doe: Person = true`

we get an error: `Type 'boolean' is not assignable to type 'Person'`

*doe*is a variable that should hold a value that should only be found in the set of Person, but here we are, going against that by trying to put a value of

*true*in it. Sure that won’t work! Values can only become elements of sets they belong to!

### What is Union Type

**A**can be a set that represents a basket of fruits, while

**B**represents a basket of candies. The type

**A U B**will therefore be a set that contains either a fruit or a candy.

**A U B**. can be aliased as

*Office Treats*if you may. This union operation means if you have a candy, it can go into the

**A U B**set. So also, if you have a fruit, it can go into

**A U B**, but wine, or beer, on the other hand, won’t be an element that can belong to the set

**A U B**.

In TypeScript, union Types are created with the vertical bar: |. So for example a union type based on union-ing a string and number will be created by writing string | number.

For example to define a double function that takes a union of string and number, we have:

```
function double(input: number | string) {
if (typeof input === "number") {
return input * 2
} else {
return `${input} ${input}`
}
}
console.log(double(3)) // prints 6
console.log(double("hoi")) // prints “hoi hoi”
```

**Flow-Sensitive Typing, Type Narrowing, and Type Guards.**

*Flow-Sensitive Typing*,

*Type Narrowing*, and

*Type Guards*. Understanding these features will go a long way in appreciating how to put union types to use.

**Flow-sensitive typing**is a feature of a type system where the type of an expression is determined based on its position in the control flow; control flow being programming language constructs like:

*if statement*,

*while statement*, etc.

**Type Narrowing**is a process of narrowing a value to a more specific type. Type narrowing depends on the results of applying constructs called

**Type Guards**.

*double*function example above, we see both flow-sensitive typing and type narrowing at play.

*typeof*operator, the

*in*operator, or custom type guards defined using

**Type Predicates**.

*typeof*operator is used as a type guard. The

*in*operator can also be used, in that case, the input type may not be a primitive type.

```
type Stack = {
id: number,
items: string[]
}
type Sound = {
bass: number
tremble: number
volume: number
}
function double(input: Stack | Sound) {
if ("items" in input) {
return {
id: input.id,
items: input.items.concat(input.items)
}
} else {
return {
bass: input.bass,
tremble: input.bass,
volume: input.volume * 2
}
}
}
```

Here we define two custom types: Sound and Stack, which then allow the use of the in operator as a type guard.

Custom type guards can also be used. This involves using Type Predicates. This will look like the following:

```
function double(input: Stack | Sound) {
if (isStack(input)) {
return {
id: input.id,
items: input.items.concat(input.items)
}
} else {
return {
bass: input.bass,
tremble: input.bass,
volume: input.volume * 2
}
}
}
// this is the type predicate
function isStack(input: any): input is Stack {
return "items" in input
}
```

The type predicate above is the isStack function:

```
function isStack(input: any): input is Stack {
return "items" in input
}
```

It is used as a predicate to determine if the type of the input value is of a particular type. Notice the return type: input is Stack, this means if the function returns true, then the type of the input can be narrowed to Stack.

This completes the quick introduction of union types, let's take a look at Intersection types next.

### What is Intersection Type

**A**to be a set of fruits: apples, oranges etc, while

**B**can be imagined to be a set of drinks: wine, sparkling water etc.

A set

**C**which is an intersection of both set

**A**and set

**C**, will therefore be a set that contains both properties of being a fruit and a drink: ie Fruit Juices.

This means any element of an intersection type will have to have properties that belong to the individual types that are involved in the intersection operation that created it.

In TypeScript, an intersection type is created using the ampersand (&)

To further illustrates, given types that defines a

*developer*and

*system admin*as follows:

```
type DevRole = {
languages: string[],
testing: string[]
}
type SysAdminRole = {
oses: string[],
automation: string[]
}
```

Then we can construct a DevOps type from these two types as follows:

`type DevOps = DevRole & SysAdminRole`

Then a value that can be assign the DevOps type can be created as follows:

```
let andy: DevOps = {
languages: ["java", "JavaScript"],
testing: ["unit", "integration", "functional"],
oses: ["linux", "solaris", "windows"],
automation: ["salt", "ansible"]
}
```

**C**formed from two types

**A**and

**B**involves having all the properties from

**A**and

**B**: that is, formed as a result of the

__union__of all the properties of

**A**and

**B**, why is it then called intersection type and not union type?.

**is not**formed by union-ing all the properties of two (or multiple) values. A union type is formed by constructing a new type that is a union of existing types. Doing this means the new type can contain any value as long as that value belongs to one or more of its constituent types. The key thing is that the union operation is performed on the types.

*Developers*and

*System Admins*, the operation itself, the intersection, happens on the type level hence why it is called an Intersection Type.

## No comments:

Post a Comment