Option-less pattern matching

Dotty implementation of pattern matching was greatly simplified compared to scalac. From a user perspective, this means that Dotty generated patterns are a lot easier to debug, as variables all show up in debug modes and positions are correctly preserved.

Dotty supports a superset of scalac's extractors.

Extractors

Extractors are objects that expose a method unapply or unapplySeq:

def unapply[A](x: T)(implicit x: B): U
def unapplySeq[A](x: T)(implicit x: B): U

Extractors expose the method unapply are called fixed-arity extractors, which work with patterns of fixed arity. Extractors expose the method unapplySeq are called variadic extractors, which enables variadic patterns.

Fixed-Arity Extractors

Fixed-arity extractors expose the following signature:

def unapply[A](x: T)(implicit x: B): U

The type U conforms to one of the following matches:

Or U conforms to the type R:

type R = {
  def isEmpty: Boolean
  def get: S
}

and S conforms to one of the following matches:

The former form of unapply has higher precedence, and single match has higher precedence over name-based match.

Variadic Extractors

Variadic extractors expose the following signature:

def unapplySeq[A](x: T)(implicit x: B): U

The type U conforms to one of the following matches:

Or U conforms to the type R:

type R = {
  def isEmpty: Boolean
  def get: S
}

and S conforms to one of the two matches above.

The former form of unapplySeq has higher priority, and sequence match has higher precedence over product-sequence match.

Boolean Match

For example:

object Even {
  def unapply(s: String): Boolean = s.size % 2 == 0
}

"even" match {
  case s @ Even() => println(s"$s has an even number of characters")
  case s          => println(s"$s has an odd number of characters")
}
// even has an even number of characters

Product Match

For example:

class FirstChars(s: String) extends Product {
  def _1 = s.charAt(0)
  def _2 = s.charAt(1)

  // Not used by pattern matching: Product is only used as a marker trait.
  def canEqual(that: Any): Boolean = ???
  def productArity: Int = ???
  def productElement(n: Int): Any = ???
}

object FirstChars {
  def unapply(s: String): FirstChars = new FirstChars(s)
}

"Hi!" match {
  case FirstChars(char1, char2) =>
    println(s"First: $char1; Second: $char2")
}
// First: H; Second: i

Single Match

class Nat(val x: Int) {
  def get: Int = x
  def isEmpty = x < 0
}

object Nat {
  def unapply(x: Int): Nat = new Nat(x)
}

5 match {
  case Nat(n) => println(s"$n is a natural number")
  case _      => ()
}
// 5 is a natural number

Name-based Match

object ProdEmpty {
  def _1: Int = ???
  def _2: String = ???
  def isEmpty = true
  def unapply(s: String): this.type = this
  def get = this
}

"" match {
  case ProdEmpty(_, _) => ???
  case _ => ()
}

Sequence Match

type X = {
  def lengthCompare(len: Int): Int // or, `def length: Int`
  def apply(i: Int): T1
  def drop(n: Int): scala.Seq[T2]
  def toSeq: scala.Seq[T3]
}
object CharList {
  def unapplySeq(s: String): Option[Seq[Char]] = Some(s.toList)
}

"example" match {
  case CharList(c1, c2, c3, c4, _, _, _) =>
    println(s"$c1,$c2,$c3,$c4")
  case _ =>
    println("Expected *exactly* 7 characters!")
}
// e,x,a,m

Product-Sequence Match

class Foo(val name: String, val children: Int *)
object Foo {
  def unapplySeq(f: Foo): Option[(String, Seq[Int])] = Some((f.name, f.children))
}

def foo(f: Foo) = f match {
  case Foo(name, ns : _*) =>
  case Foo(name, x, y, ns : _*) =>
}

There are plans for further simplification, in particular to factor out product match and name-based match into a single type of extractor.