Scala - Intersection Types
Scala Intersection Types are used to define a type that combines multiple types. So the variable satisfies all the specified types. Intersection types used to specify that a value should have multiple types.
Intersection types are composite data types. Intersection types are used to represent values of different data types at the same time. The & operator is used to create intersection types. Intersection types are built-in types in Scala 3.
Declaring Intersection Types
The following is the syntax for declaring an intersection type variable.
Syntax
def exampleMethod(x: Int & String): Unit = {
x match {
case i: Int => println(s"Integer: $i")
case s: String => println(s"String: $s")
}
}
Here, exampleMethod is declared with a parameter x that must be both an Int and a String. The intersection type Int & String specifies that x must satisfy both types.
Intersection Types in Methods
Below is an example program showing how to create and use a method with an intersection type parameter.
Example
trait Printable {
def print(): Unit
}
trait Showable {
def show(): Unit
}
object Demo {
def main(args: Array[String]) = {
def exampleMethod(x: Printable & Showable): Unit = {
x.print()
x.show()
}
// For both Printable and Showable
val item = new Printable with Showable {
def print() = println("Zara")
def show() = println("Nuha")
}
// Call method with instance
exampleMethod(item)
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha
Intersection Types in Function Parameters
You can also use intersection types in function parameters. So, functions can accept arguments that satisfy multiple types.
Example
Try following example of intersection types in function parameters -
trait Readable {
def read(): Unit
}
trait Writable {
def write(): Unit
}
object Demo {
def main(args: Array[String]) = {
def process(input: Readable & Writable): Unit = {
input.read()
input.write()
}
// For both Readable and Writable
val file = new Readable with Writable {
def read() = println("Zara")
def write() = println("Nuha")
}
// Call function with instance
process(file)
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha
Intersection Types with Type Aliases
You can use type aliases to simplify the usage of intersection types. You can use it when these are used frequently.
Example
Try following example of intersection types with Type aliases -
trait Readable {
def read(): Unit
}
trait Writable {
def write(): Unit
}
object Demo {
type ReadableWritable = Readable & Writable
def main(args: Array[String]) = {
def process(input: ReadableWritable): Unit = {
input.read()
input.write()
}
// For both Readable and Writable
val file = new Readable with Writable {
def read() = println("Zara")
def write() = println("Nuha")
}
// Call function with instance
process(file)
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha
Using Intersection Types in Constructors
You can use intersection types in class constructors, so instances satisfy multiple types. Try following example using intersection types in constructors -
Example
trait Readable {
def read(): Unit
}
trait Writable {
def write(): Unit
}
class FileHandler(input: Readable & Writable) {
def process(): Unit = {
input.read()
input.write()
}
}
object Demo {
def main(args: Array[String]) = {
// For both Readable and Writable
val file = new Readable with Writable {
def read() = println("Zara")
def write() = println("Nuha")
}
// FileHandler with instance
val handler = new FileHandler(file)
handler.process()
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha
Intersection Types with Higher-Order Functions
You can use intersection types combined with higher-order functions to process elements that satisfy multiple types in a collection.
Example
Try following example of intersection types with higher order function -
trait Displayable {
def display(): Unit
}
trait Loggable {
def log(): Unit
}
object Demo {
def main(args: Array[String]) = {
val items: List[Displayable & Loggable] = List(
new Displayable with Loggable {
def display() = println("Zara")
def log() = println("Nuha")
},
new Displayable with Loggable {
def display() = println("Ayan")
def log() = println("Maira")
}
)
// Process each item
items.foreach { item =>
item.display()
item.log()
}
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha Ayan Maira
Intersection Types with Default Parameter Values
You can also use intersection types with default parameter values in functions and methods. So, there will be flexibility in how the function can be called.
Example
Try following example of intersection types with default parameter values -
trait Printable {
def print(): Unit
}
trait Showable {
def show(): Unit
}
object Demo {
def main(args: Array[String]) = {
def displayMessage(item: Printable & Showable = new Printable with Showable {
def print() = println("Zara")
def show() = println("Nuha")
}): Unit = {
item.print()
item.show()
}
// Call function with default parameter
displayMessage()
// For both Printable and Showable
val customItem = new Printable with Showable {
def print() = println("Ayan")
def show() = println("Maira")
}
// Call function with a custom instance
displayMessage(customItem)
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha Ayan Maira
Intersection Types in Collections
You can use intersection types within collections for lists, sets, and other collections to hold elements that satisfy multiple types.
Example
Try following example of intersection types in collections -
trait Readable {
def read(): Unit
}
trait Writable {
def write(): Unit
}
object Demo {
def main(args: Array[String]) = {
val collection: List[Readable & Writable] = List(
new Readable with Writable {
def read() = println("Zara")
def write() = println("Nuha")
},
new Readable with Writable {
def read() = println("Ayan")
def write() = println("Maira")
}
)
collection.foreach { item =>
item.read()
item.write()
}
}
}
Save the above program in Demo.scala. Use the following commands to compile and execute this program.
Command
> scalac Demo.scala > scala Demo
Output
Zara Nuha Ayan Maira
Intersection Types Summary
- Intersection types in Scala combine multiple types. So, a variable satisfies all given types.
- Intersection types are built-in types in Scala 3 and & operator is used to create it.
- You can use intersection types in method parameters, function parameters, class constructors, and collections.
- Type aliases can simplify the usage of intersection types.
- You can use intersection types to enhance type safety with values that satisfy multiple types.
- You can also use intersection types with higher-order functions, default parameter values, pattern matching, and collections to process elements that satisfy multiple types.