Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.

Latest commit

 

History

History
262 lines (182 loc) · 6.49 KB

10-intersection-and-union-types.md

File metadata and controls

262 lines (182 loc) · 6.49 KB

­

­

­

­

NEW TYPES IN SCALA 3

Union and Intersection Types


Intersection & Union Types

­

  • Scala 3 introduces new types:
    • Intersection Types
    • Union Types
  • Let's have a look at both

Intersection Types

­

  • An Intersection Type is created using the & operator on two types
  • For example, using some types S and T:
type S            // Some type S
type T            // Some type T
type ST = S & T   // Intersection Type ST which has all the members of both S & T
  • From the docs:

Intersection types

  • & is commutative: A & B is the same type as B & A

Intersection Types - Example

­

  • A simple example:
trait Growable:
  def growBy(percent: Int): this.type =
    println(s"Growing by $percent%")
    this

trait Paintable:
  def paint(color: Int): this.type =
    println(s"Painted with color $color")
    this


def resizeAndPaint(obj: Growable & Paintable): Unit =
  obj.growBy(20).paint(0x10FF00).growBy(40).paint(0x0010FF)
 
  • and using it:
scala> resizeAndPaint(new Growable with Paintable)
     Growing by 20%
     Painted with color 1113856
     Growing by 40%
     Painted with color 4351

Union Types

­

  • A Union Type is created using the | operator on two types
  • For example, using some types S and T:
type S            // Some type S
type T            // Some type T
type ST = S | T   // Union Type ST 
  • From the docs:

Intersection types

  • | is commutative: A | B is the same type as B | A
  • Union and Intersection Types are duals of each other
  • The least upper bound (lub) of a set of types is the union of these types

Union Types - Example I

­

  • A simple example
enum Tools:
  case Hammer(size: Int)
  case Screwdriver(size: Int)

enum ToolSupplies:
  case Nail(size: Int)
  case Screw(size: Int)

def printIt(t: Tools | ToolSupplies): Unit = t match
  case tool: Tools          => println(s"Got a tool: $tool")
  case supply: ToolSupplies => println(s"Got a supply: $supply")
 
  • and using it:
scala> import Tools._
       import ToolSupplies._

scala> printIt(Hammer(6))
Got a tool: Hammer(6)

scala> printIt(Nail(9))
Got a supply: Nail(9)

Union Types - Example II

Akka Typed Actors - encoding

­

  • The behaviour of an Actor is implemented via the akka.actor.typed.Behavior API

­

  • The Behavior takes a type parameter which corresponds to the message types the Behavior can and will handle as formally defined in its Command protocol

­

  • On top of the commands an Actor can process, it will also have to process Responses from other Actors as defined in the latter's Response protocol

­

  • How do we encode a behaviour so that it can process both commands and responses internally, while limiting the external protocol to Command?

Union Types - Example II

Akka Typed Actors - encoding

­

  • Let's look at an example with two Actors: a PingPong Actor and a Pinger Actor:

ping-pong Actor and protocol


Union Types - Example II

Akka Typed Actors - encoding

  • The protocols for the Pinger and the PingPong Actors

ping-pong protocol

  • The problem to solve is how to extend the Pinger's behaviour so that it "understands" the Pong response

Union Types - Example II

Akka Typed Actors - encoding

  • Akka Typed 2.6 uses a message adapter/response wrapper approach

ping-pong protocol message wrappers

  • The Pong message is wrapped in a WrappedPongResponse message

Union Types - Example II

Akka Typed Actors - encoding


Union Types - Example II

Akka Typed Actors - encoding


Union Types - Example II

Akka Typed Actors - encoding

  • Message adapters with Response wrappers solve the issue, but
    • this is quite convoluted and it adds a lot of boilerplate
    • requires an extra effort from anyone trying to understand the code

­

  • There is a better solution using Scala 3's Union Types!

­

  • Let's explore that

Union Types - Example II

Akka Typed Actors - encoding

  • An example (applicable to Akka)
enum Command:
  case Reset
  case Run(times: Int)

enum Response:
  case RunFailed(reason: String)
  case RunFinished

trait Behavior[-A]:
  def treatMsg(message: A): Unit = println(s"Treating message: $message")

type CommandAndResponse = Command | Response

// implicitly[Behavior[CommandAndResponse] <:< Behavior[Command]]
val internalBehavior: Behavior[CommandAndResponse] = new Behavior[CommandAndResponse]{}
val externalBehavior: Behavior[Command] = internalBehavior   // Contravariance at work
 
internalBehavior.treatMsg(Command.Reset)
internalBehavior.treatMsg(Command.Run(5))
internalBehavior.treatMsg(Response.RunFailed("Too much to do"))
internalBehavior.treatMsg(Response.RunFinished)
  
externalBehavior.treatMsg(Command.Reset)
externalBehavior.treatMsg(Command.Run(110))
externalBehavior.treatMsg(Response.RunFailed("Too much to do"))   // Doesn't compile
 

Summary

­

  • In this chapter, we have taken a closer look at two new Types in Scala 3:
    • Union Types
    • Intersection Types

­

  • Union Types define a type that is a Least Upper Bound on two [possibly unrelated] types

­

  • Intersection Types define a type that is a Highest Lower Bound on two types

Using Union Types

­

  • In this exercise, we will utilise Union Types to vastly simplify the handling of responses to messages sent by an Akka actor
    • Make sure you're positioned at exercise "union types"
    • Follow the exercise instructions provided in the README.md file in the code folder