Used on types, the
| operator creates a so-called union type.
A | B represents values that are either of the type
A or of the type
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
case class Username(name: String) case class Password(hash: Hash) def help(id: Username | Password) = val user = id match case Username(name) => lookupName(name) case Password(hash) => lookupPassword(hash) // more code here ...
We implement the method
help by distinguishing between the two alternatives using pattern matching.
This code is a flexible and type-safe solution.
If you attempt to pass in a type other than a
Password, the compiler flags it as an error:
help("hi") // error: Found: ("hi" : String) // Required: Username | Password
You’ll also get an error if you attempt to add a
case to the
match expression that doesn’t match the
case 1.0 = > ??? // ERROR: this line won’t compile
Alternative to Union Types
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.
Pre-planning the Class Hierarchy
Other languages would require pre-planning of the class hierarchy, like the following example illustrates:
trait UsernameOrPassword case class Username(name: String) extends UsernameOrPassword case class Password(hash: Hash) extends UsernameOrPassword def help(id: UsernameOrPassword) = ...
Pre-planning does not scale very well since, for example, requirements of API users might not be foreseeable.
Additionally, cluttering the type hierarchy with marker traits like
UsernameOrPassword also makes the code more difficult to read.
Another alternative is to define a separate enumeration type like:
enum UsernameOrPassword: case IsUsername(u: Username) case IsPassword(p: Password)
UsernameOrPassword represents a tagged union of
However, this way of modeling the union requires explicit wrapping and unwrapping and, for instance,
Username is not a subtype of
Inference of Union Types
The compiler assigns a union type to an expression only if such a type is explicitly given. For instance, given these values:
val name = Username("Eve") // name: Username = Username(Eve) val password = Password(123) // password: Password = Password(123)
This REPL example shows how a union type can be used when binding a variable to the result of an
scala> val a = if (true) name else password val a: Object = Username(Eve) scala> val b: Password | Username = if (true) name else password val b: Password | Username = Username(Eve)
The type of
Object, which is a supertype of
Password, but not the least supertype,
Password | Username.
If you want the least supertype you have to give it explicitly, as is done for
Union types are duals of intersection types. And like
&with intersection types,
|is also commutative:
A | Bis the same type as
B | A.