Union Types
A union type A | B
includes all values of both types.
trait ID
case class UserName(name: String) extends ID
case class Password(hash: Hash) extends ID
def help(id: UserName | Password) =
val user = id match
case UserName(name) => lookupName(name)
case Password(hash) => lookupPassword(hash)
...
Union types are duals of intersection types. |
is commutative: A | B
is the same type as B | A
.
The compiler will assign a union type to an expression only if such a type is explicitly given or if the common supertype of all alternatives is transparent.
This can be seen in the following REPL transcript:
scala> val password = Password(123)
val password: Password = Password(123)
scala> val name = UserName("Eve")
val name: UserName = UserName(Eve)
scala> if true then name else password
val res1: ID = UserName(Eve)
scala> val either: Password | UserName = if true then name else password
val either: UserName | Password = UserName(Eve)
The type of res1
is ID
, which is a supertype of UserName
and Password
, but not the least supertype UserName | Password
. If we want the least supertype, we have to give it explicitly, as is done for the type of either
.
The inference behavior changes if the common supertrait ID
is declared transparent
:
transparent trait ID
In that case the union type is not widened.
scala> if true then name else password
val res2: UserName | Password = UserName(Eve)
The more precise union type is also inferred if UserName
and Password
are declared without an explicit parent, since in that case their implied superclass is Object
, which is among the classes that are assumed to be transparent. See Transparent Traits and Classes for a list of such classes.
case class UserName(name: String)
case class Password(hash: Hash)
scala> if true then UserName("Eve") else Password(123)
val res3: UserName | Password = UserName(Eve)