I've learnt a number of interesting things about the Scala compiler recently, the first of which is the @annotation.tailrec
annotation - if you add this to a method which you expect to be tail call optimised, and the compiler can't perform the optimisation, then it will generate an error. Usage of it looks like this:
def factorial(n: Int): Int = {
@annotation.tailrec
def go(n: Int, acc: Int): Int =
if (n <= 0) acc
else go(n-1, n*acc)
go(n, 1)
}
The other one I came across when doing an exercise with currying was where I had written the following two functions:
def curry1[A,B,C](f: (A, B) => C): A => (B => C) =
(a: A) => f(a, _)
def curry2[A,B,C](f: (A, B) => C): A => (B => C) =
(a: A) => (b: B) => f(a, b)
I wanted to prove that the two functions were equivalent. After some research I found that I could pass a flag to the Scala compiler when compiling my class like this:
scalac -Xprint:typer Currying.scala
This command prints out the abstract syntax tree of the code after the compiler phase where types are assigned has been run. This is what was printed out:
[[syntax trees at end of refchecks]] // Currying.scala
package {
object Currying extends scala.AnyRef {
def (): Currying.type = {
Currying.super.();
()
};
def curry1[A, B, C](f: (A, B) => C): A => (B => C) = ((a: A) => ((x$1: B) => f.apply(a, x$1)));
def curry2[A, B, C](f: (A, B) => C): A => (B => C) = ((a: A) => ((b: B) => f.apply(a, b)))
}
}
So yes, they are equivalent!
Then, I started wondering how many phases there were in the Scala compiler, and found that the command:
scalac -Xshow-phases
will list each phase. I am using version 2.11.1 of the compiler and my output looked like this:
phase name id description ---------- -- ----------- parser 1 parse source into ASTs, perform simple desugaring namer 2 resolve names, attach symbols to named trees packageobjects 3 load package objects typer 4 the meat and potatoes: type the trees patmat 5 translate match expressions superaccessors 6 add super accessors in traits and nested classes extmethods 7 add extension methods for inline classes pickler 8 serialize symbol tables refchecks 9 reference/override checking, translate nested objects uncurry 10 uncurry, translate function values to anonymous classes tailcalls 11 replace tail calls by jumps specialize 12 @specialized-driven class and method specialization explicitouter 13 this refs to outer pointers erasure 14 erase types, add interfaces for traits posterasure 15 clean up erased inline classes lazyvals 16 allocate bitmaps, translate lazy vals into lazified defs lambdalift 17 move nested functions to top level constructors 18 move field definitions into constructors flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls delambdafy 22 remove lambdas icode 23 generate portable intermediate code jvm 24 generate JVM bytecode terminal 25 the last phase during a compilation run
Who knew there were so many different phases, and how interesting!