Max Heiber
Max Heiber

Reputation: 15502

Succinct way to use GADTs for exhaustiveness-checking in Scala?

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 *)

scastie

Is there a more succinct way to express this in Scala, without the phantom CircleMarker and RectangleMarker traits?

Upvotes: 0

Views: 108

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51658

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

Upvotes: 1

Related Questions