This is an excerpt from the Scala Cookbook, 2nd Edition. This is Recipe 23.9, Simulating Dynamic Typing with Union Types.
You have a situation where it would be helpful if a value could represent one of several different types, without requiring those types to be part of a class hierarchy. Because the types aren’t part of a class hierarchy, you’re essentially declaring them in a dynamic way, even though Scala is a statically-typed language.
In Scala 3, a union type is a value that can be one of several different types. Union types can be used in several ways.
In one use, union types let us write functions where a parameter can potentially be one of several different types. For example, this function, which implements the Perl definition of true and false, takes a parameter that can be either an
Int or a
// Perl version of "true" def isTrue(a: Int | String): Boolean = a match case 0 => false case "0" => false case "" => false case _ => true
String don’t share any direct parent types — at least not until you go up the class hierarchy to
Any — this is a type-safe solution. The compiler is smart enough to know that if I attempt to add a case that tests the parameter against a
Double, it will be flagged as an error:
case 1.0 = > false // ERROR: this line won’t compile
In this example, stating that the parameter
a is either an
Int or a
String is a way of dynamic typing in a statically-typed language. If you wanted to match additional types you could just list them all, even if they don’t share a common type hierarchy (besides
class Person class Planet class BeachBall // the type parameter: a: Int | String | Person | Planet | BeachBall
Note: The only way to write that function prior to Scala 3 was to make the function parameter have the type
Any, and then match the
Stringcases in the match expression. (In Scala 2 you would use the type
Any, and in Scala 3 you would use the type
In other uses, a function can return a union type, and a variable can be a union type. For example, this function returns a union type:
def aFunction(): Int | String = val x = scala.util.Random.nextInt(100) if (x < 50) then x else s"string: $x"
You can then assign the result of that function to a variable:
val x = aFunction() val x: Int | String = aFunction()
In either of those uses,
x will have the type
Int | String, and will contain an
A union type is a value that can be one of several different types. As shown, it’s a way to create function parameters, function return values, and variables that can be one of many types, without requiring traditional forms of inheritance for that type. Union types provide an ad-hoc way of combining types.
Combining union types with literal types
In another use, you can combine union types with literal types to create code like this:
// create a union type from two literal types type Bool = "True" | "False" // a function to use the union type def handle(b: Bool): Unit = b match case "True" => println("true") case "False" => println("false") handle("True") handle("False") handle("Fudge") // error, won’t compile // this also works val t: Bool = "True" val f: Bool = "False" val x: Bool = "Fudge" // error, won’t compile
The ability to create your own types using the combined power of literal types and union types gives you more flexibility to craft your own APIs.