I'm looking for the same behavior as the following OCaml code, where the compiler understands the match is exhaustive because we've expressed that the two scrutinees must have the same type:
type circle
type rectangle
type _ figure =
| Circle : int -> circle figure
| Rectangle : int * int -> rectangle figure
let equal_figure : type a. a figure -> a figure -> bool = fun f1 f2 -> match (f1, f2) with
| Circle r1, Circle r2 -> Int.(r1 = r2)
| Rectangle (x1, y1), Rectangle (x2, y2) -> Int.(x1 = x2 && y1 = y2)
(* the compiler knows this match is exhaustive *)
I can port the example directly to Scala and the exhaustiveness-checker does the right thing:
sealed trait CircleMarker
sealed trait RectangleMarker
enum Fig[T]:
case Circle(r: Int) extends Fig[CircleMarker]
case Rectangle(x: Int, y: Int) extends Fig[RectangleMarker]
def equalFig[T](f1: Fig[T], f2: Fig[T]): Boolean = (f1, f2) match
case (Fig.Circle(r1), Fig.Circle(r2)) => r1 == r2
case (Fig.Rectangle(x1, y1), Fig.Rectangle(x2, y2)) => x1 == x2 && y1 == y2
(* the compiler knows this match is exhaustive *)
Is there a more succinct way to express this in Scala, without the phantom CircleMarker
and RectangleMarker
traits?
You can try F-bounds
enum Fig[T <: Fig[T]]:
case Circle(r: Int) extends Fig[Circle]
case Rectangle(x: Int, y: Int) extends Fig[Rectangle]
// sealed trait Fig[T <: Fig[T]]
// object Fig:
// case class Circle(r: Int) extends Fig[Circle]
// case class Rectangle(x: Int, y: Int) extends Fig[Rectangle]
def equalFig[T <: Fig[T]](f1: Fig[T], f2: Fig[T]): Boolean = (f1, f2) match
case (Fig.Circle(r1), Fig.Circle(r2)) => r1 == r2
case (Fig.Rectangle(x1, y1), Fig.Rectangle(x2, y2)) => x1 == x2 && y1 == y2
// def equalFig[T <: Fig[T]](f1: Fig[T], f2: Fig[T]): Boolean = f1 == f2
equalFig(Fig.Circle(1), Fig.Circle(1)) // true
equalFig(Fig.Circle(1), Fig.Circle(2)) // false
equalFig(Fig.Rectangle(1, 2), Fig.Rectangle(1, 2)) // true
equalFig(Fig.Rectangle(1, 2), Fig.Rectangle(1, 3)) // false
// equalFig(Fig.Circle(1), Fig.Rectangle(1, 2)) // doesn't compile