1

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.

Make42
  • 12,236
  • 24
  • 79
  • 155
  • I don't know if pattern matching is _actually_ desugared, but it could in principle be transformed into some `if`s using [`unapply`](http://stackoverflow.com/a/36860003/1346276). – phipsgabler May 03 '16 at 14:02

1 Answers1

5

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

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
Dan Barowy
  • 2,270
  • 24
  • 35
  • Can you explain: Is `Number(n) => n` a scala function and the `match`-clause read as `case` ( `Number(n) => n` ) or should it be read as ( `case Number(n)` ) ( `=>` ) ( `n` )? Or even differently? – Make42 May 04 '16 at 12:29
  • Statements of the form `e match { case p1 => f1; ...; case pn => fn }` are like magical `switch` statements. If `e` _is a_ `p1` then run the function `p1 => f1`, otherwise if `e` _is a_ `p1+1` then run the function `p1+1 => f1+1`, and so on. In practice, you don't often need to know how they _actually_ work (and I couldn't tell you the rules myself). I can tell you that in your `match` example above, the compiler generates bytecode that checks the type of `e` against each pattern (at runtime; e.g., `Number`) using `instanceof` and then executes the code on the right hand side. – Dan Barowy May 04 '16 at 17:05
  • Wrt your comment about "I don't see that `n` in `case Number(n)` is anywhere defined and/or bound to a value", here's how I think about it: `Number(n)` is a "deconstructor". Scala calls them "extractors". If `e` happens to be a `Number`, then `n` will be bound to whatever value is defined by the extractor. Since you used a `case class`, then that extractor (`unapply`) is automatically generated for you and is basically the converse of the constructor. So `n` in the expression on the right hand side is bound to the value `n` in the `case class Number`. Make sense? – Dan Barowy May 04 '16 at 17:12
  • Regarding your last post: So the extractor is kind of turning the class "inside out" revealing and matching it's class members with the values of the case? – Make42 May 07 '16 at 14:56
  • I was told that using `instanceof` is bad practice (I think Odersky said that as well) - though I forgot why. In Scala using `match`-constructs does not seem to be bad practise. Why? – Make42 May 07 '16 at 14:56
  • I suspect that the advice to avoid `instanceof` is for maintenance reasons. If you use `instanceof`and you later add a new class it is easy to miss a case (i.e., it won't be exhaustive). `match` will in many cases ensure that if you miss a case, you are warned by the compiler (e.g., when you use `sealed` traits). – Dan Barowy May 13 '16 at 18:34