Skip to content

Commit 81893d9

Browse files
committed
Add code tabs for _overviews/scala3-book/types-union.md
1 parent 3789cf6 commit 81893d9

File tree

1 file changed

+59
-0
lines changed

1 file changed

+59
-0
lines changed

_overviews/scala3-book/types-union.md

+59
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@ num: 51
77
previous-page: types-intersection
88
next-page: types-adts-gadts
99
---
10+
<span class="tag tag-inline">Scala 3 only</span>
1011

1112
Used on types, the `|` operator creates a so-called _union type_.
1213
The type `A | B` represents values that are **either** of the type `A` **or** of the type `B`.
1314

1415
In the following example, the `help` method accepts a parameter named `id` of the union type `Username | Password`, that can be either a `Username` or a `Password`:
1516

17+
{% tabs union-user %}
18+
19+
{% tab 'Scala 3 Only' %}
20+
1621
```scala
1722
case class Username(name: String)
1823
case class Password(hash: Hash)
@@ -23,59 +28,109 @@ def help(id: Username | Password) =
2328
case Password(hash) => lookupPassword(hash)
2429
// more code here ...
2530
```
31+
{% endtab %}
32+
33+
{% endtabs %}
34+
2635
We implement the method `help` by distinguishing between the two alternatives using pattern matching.
2736

2837
This code is a flexible and type-safe solution.
2938
If you attempt to pass in a type other than a `Username` or `Password`, the compiler flags it as an error:
3039

40+
{% tabs union-error-1 %}
41+
42+
{% tab 'Scala 3 Only' %}
43+
3144
```scala
3245
help("hi") // error: Found: ("hi" : String)
3346
// Required: Username | Password
3447
```
3548

49+
{% endtab %}
50+
51+
{% endtabs %}
52+
3653
You’ll also get an error if you attempt to add a `case` to the `match` expression that doesn’t match the `Username` or `Password` types:
3754

55+
{% tabs union-error-2 %}
56+
57+
{% tab 'Scala 3 Only' %}
58+
3859
```scala
3960
case 1.0 => ??? // ERROR: this line won’t compile
4061
```
4162

63+
{% endtab %}
64+
65+
{% endtabs %}
66+
4267
### Alternative to Union Types
4368
As shown, union types can be used to represent alternatives of several different types, without requiring those types to be part of a custom-crafted class hierarchy, or requiring explicit wrapping.
4469

4570
#### Pre-planning the Class Hierarchy
4671
Other languages would require pre-planning of the class hierarchy, like the following example illustrates:
4772

73+
{% tabs union-preplanning %}
74+
75+
{% tab 'Scala 3 Only' %}
76+
4877
```scala
4978
trait UsernameOrPassword
5079
case class Username(name: String) extends UsernameOrPassword
5180
case class Password(hash: Hash) extends UsernameOrPassword
5281
def help(id: UsernameOrPassword) = ...
5382
```
83+
84+
{% endtab %}
85+
86+
{% endtabs %}
87+
5488
Pre-planning does not scale very well since, for example, requirements of API users might not be foreseeable.
5589
Additionally, cluttering the type hierarchy with marker traits like `UsernameOrPassword` also makes the code more difficult to read.
5690

5791
#### Tagged Unions
5892
Another alternative is to define a separate enumeration type like:
5993

94+
{% tabs union-tagged-unions %}
95+
96+
{% tab 'Scala 3 Only' %}
97+
6098
```scala
6199
enum UsernameOrPassword:
62100
case IsUsername(u: Username)
63101
case IsPassword(p: Password)
64102
```
103+
104+
{% endtab %}
105+
106+
{% endtabs %}
107+
65108
The enumeration `UsernameOrPassword` represents a _tagged_ union of `Username` and `Password`.
66109
However, this way of modeling the union requires _explicit wrapping and unwrapping_ and, for instance, `Username` is **not** a subtype of `UsernameOrPassword`.
67110

68111
### Inference of Union Types
69112
The compiler assigns a union type to an expression _only if_ such a type is explicitly given.
70113
For instance, given these values:
71114

115+
{% tabs union-tagged-unions-example %}
116+
117+
{% tab 'Scala 3 Only' %}
118+
72119
```scala
73120
val name = Username("Eve") // name: Username = Username(Eve)
74121
val password = Password(123) // password: Password = Password(123)
75122
```
76123

124+
{% endtab %}
125+
126+
{% endtabs %}
127+
77128
This REPL example shows how a union type can be used when binding a variable to the result of an `if`/`else` expression:
78129

130+
{% tabs union-tagged-unions-example-if %}
131+
132+
{% tab 'Scala 3 Only' %}
133+
79134
````
80135
scala> val a = if true then name else password
81136
val a: Object = Username(Eve)
@@ -84,6 +139,10 @@ scala> val b: Password | Username = if true then name else password
84139
val b: Password | Username = Username(Eve)
85140
````
86141

142+
{% endtab %}
143+
144+
{% endtabs %}
145+
87146
The type of `a` is `Object`, which is a supertype of `Username` and `Password`, but not the *least* supertype, `Password | Username`.
88147
If you want the least supertype you have to give it explicitly, as is done for `b`.
89148

0 commit comments

Comments
 (0)