1
votes

In

trait Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

object CaseExample {
  def eval(e: Expr): Int = e match {
    case Number(n) => n
    case Sum(e1, e2) => eval(e1) + eval(e2)
  }
  def main(args: Array[String]) {
    println(eval(Sum(Number(1), Number(2))))       //> 3
  }
}

there is quite a bit of syntactic sugar going on. I get that case is implicitly creating two objects

object Number extends Expr {
  def apply(n: Int) = new Number(n)
}
object Sum extends Expr {
  def apply(e1: Expr, e2: Expr) = new Sum(e1, e2)
}

and that is why we can write e.g. Sum(...) and still instantiate an object via a class, since Sum(...) is also syntactic sugar for Sum.apply(...).

Am I right that match construct is also syntactic sugar? If it is, how is - e.g. case Number(n) - rewritten by the compiler?

I am asking, because I don't see that n in case Number(n) is anywhere defined and/or bound to a value. Strangely enough, in a match construct the case of the first letter matters (if it where upper case it would be a constant). This is strange because as far as I know this is only in a match construct of relevance, so I have no idea how this would be de-sugared.

1
I don't know if pattern matching is actually desugared, but it could in principle be transformed into some ifs using unapply.phipsgabler

1 Answers

5
votes

Yes, match is syntactic sugar. It calls the unapply method on your object. Daniel Westheide has a nice blog post about it.

Specifically, when you define your case class for Number, here's what the compiler actually generates:

case class Number(n: scala.Int) extends scala.AnyRef with Expr with scala.Product with scala.Serializable {
  val n: scala.Int = { /* compiled code */ }
  /* omitted for brevity */
}
object Number extends scala.runtime.AbstractFunction1[scala.Int, Number] with scala.Serializable {
  def this() = { /* compiled code */ }
  final override def toString(): java.lang.String = { /* compiled code */ }
  def apply(n: scala.Int): Number = { /* compiled code */ }
  def unapply(x$0: Number): scala.Option[scala.Int] = { /* compiled code */ }
}

As you can see, the Number companion object comes with a compiler-generated unapply method.

You can find a thorough description of the design of Scala's pattern matching facility here.

-- EDIT --

If you want to see the actual, compiler generated code, run scalac -print Number.scala. Here are the relevant bits:

<synthetic> object Number extends scala.runtime.AbstractFunction1 with Serializable {
  /* ... */
  case <synthetic> def unapply(x$0: Number): Option = if (x$0.==(null))
    scala.this.None
  else
    new Some(scala.Int.box(x$0.n()));
  /* ... */
}

If you write a match expression, you can similarly run scalac -print to see how the match itself is desugared (basically: if and else expressions).