15. Annotations - RobertMakyla/scalaWiki GitHub Wiki

KEY POINTS:

You can annotate classes, methods, fields, local variables, parameters, expressions, type parameters, and types. With expressions and types, the annotation follows the annotated item. Annotations have the form @Annotation, @Annotation(value), or @Annotation(name1 = value1, ...). @volatile, @transient, @strictfp, and @native generate the equivalent Java modifiers. Use @throws to generate Java-compatible throws specifications. The @tailrec annotation lets you verify that a recursive function uses tail call optimization. The assert function takes advantage of the @elidable annotation. You can optionally remove assertions from your Scala programs. Use the @deprecated annotation to mark deprecated features.

What Can Be Annotated

 Classes, methods, fields, local variables, and parameters, just like in Java. Also:

 Expressions:

     ( myMap.get(key): @unchecked ) match { ... }    // The expression myMap.get(key) is annotated

 Type parameters:

     class MyContainer[@specialized T]

 Types

     String @cps[Unit]      // String type is annotated

Annotation Arguments

     @Test(timeout = 100, expected = classOf[IOException])

     @Named(value = "creds")
     @Named("creds")                 // I can omit the name if the name is 'value'


     @Test
     def testSomeFeature() { ... }

 Is equivalent to:

     @Test(timeout = 0, expected = classOf[org.junit.Test.None])
     def testSomeFeature() { ... }

Java Annotations

     class Credentials( @NotNull @BeanProperty var username: String )

 This gives us:

 - The constructor parameter

 - The private instance field

 - The accessor method username
 - The mutator method username_=

 - The bean accessor getUsername
 - The bean mutator setUsername


     @volatile var done = false   //  may change between different accesses, by different threads

     @transient var recentLookups = new HashMap[String, String]   // not serialized

     @strictfp def calculate(x: Double) = ...   // floating-point calculations with IEEE

     @native def win32RegKeys(path: String):Int  // method implemented in other language than Java,
                                                // through Java Native Interface (JNI)

     @cloneable class Employee              // instead of Cloneable interface
     @remote class Employee                // instead of java.rmi.Remote interface

 The @serializable annotation is deprecated. Extend the scala.Serializable trait instead.

     @SerialVersionUID(6157032470129070425L)
     class Employee extends Person with Serializable

Checked Exceptions

 In scala I don't declare checked exceptions that can be thrown.
 So when I call scala code from java, the scala code should have:

     class Book {
         @throws( classOf[IOException] ) def read(filename: String) { ... }
         ...
     }

Variable Arguments

 When I call scala code (with var args) from java, the scala code should have:

     @varargs def process(args: String*)

Optimizations - Tail Recursion

 Recursion occupies stack memory. And in Functional programming we use it often.

     def sum(xs: Seq[Int]): Int =  if (xs.isEmpty) 0 else xs.head + sum(xs.tail)

     sum(1 to 10)      // OK
     sum(1 to 100000)  // --> StackOverflowException

 This method cannot be optimized because the last step of the computation is addition, not the recursive call.
 But a slight transformation can be optimized:

     def sum2(xs: Seq[Int], total: Int): Int =  if (xs.isEmpty) total else sum2(xs.tail, xs.head + total)

 The 'total' sum is passed as a parameter.
 Since the last step of the computation is a recursive call to the same method,
 it can be transformed into a loop to the top of the method.
 The Scala compiler automatically applies the 'tail recursion' optimization to the second method.

     sum2(1 to 100000, 0)  // OK

 Sometimes it is blocked with no reason. To force optimization:

     @tailrec def sum2 ...

 Then if compiler cannot apply optimization, there will be error. e.g.:

     // could not optimize @tailrec annotated method sum2: it is neither private nor final so can be overridden

Tail Recursion - recursive sum example:

     def sum( n:Int): Int = if (n==0) 0 else n+sum(n-1)    // bad performance

 transformed into tail recursion:

     def sum( n:Int, total:Int): Int = if (n==0) total else sum(n-1, total + n)

     sum(4,0)       //  --> 1+2+3+4 = 10    tail-recursive call

     explanation of 'total + n' :
             'n' is integer that starts at 'n' and changes (decrements) each step
             'total' is current result - at the beginning is 0
             so each step we need to '+' decrementing 'n' to 'total' current result
             (to get sum)

Tail Recursion - factorial (silnia) example:

     def factorial( n:Int): Int = if (n==0) 1 else n * factorial(n-1)    // bad performance

 transformed into tail recursion:

     def factorial( n:Int, total:Int): Int = if (n==0) total else factorial(n-1, total*n)

     factorial(3,1)       //  --> 1*2*3=6   tail-recursive call

     explanation of 'total * n' :
             'n' is integer that starts at 'n' and changes (decrements) each step
             'total' is current result - at the beginning is 1
             so each step we need to 'multiply' decrementing 'n' with 'total' current result
             (to get factorial)

Optimizations - Jump Table Generation (tableswitch)

 Scala attempts to transform case clause into tableswitch (better for perf).
 The '@switch' provides a warning at compile time if the switch can't be compiled to a tableswitch.

     (n: @switch) match {
         case 0 => "Zero"
         case 1 => "One"
         case _ => "?"
     }

Optimizations - Eliding Methods

 The @elidable annotation flags methods that can be removed in production code. e.g.:

     @elidable(500) def dump(arg:Int) { ... }

 Compiling that removes this method:

     scalac -Xelide-below 800 myprog.scala

Optimizations - Specialization for Primitive Types

 Generic types:

     def allDifferent[T](x: T, y: T, z: T) = x != y && x != z && y != z

     allDifferent(1,2,3)  //wraps arguments to java.lang.Integer

 We use @specialized to mark that we want to accept only scala types:

     def allDifferent[@specialized T](x: T, y: T, z: T) = x != y && x != z && y != z
          //  acceptable types: Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double

     def allDifferent[@specialized(Long, Double) T](x: T, y: T, z: T) = x != y && x != z && y != z
          //  acceptable types: Long, Double

Annotations for Errors and Warnings

     @deprecated(message = "Use factorial(n: BigInt) instead")      // generates warning
     def factorial(n: Int): Int = ...

 The @unchecked annotation suppresses a warning that a match is not exhaustive.
 So compiler won't complain that there is no Nil option. But if lst is Nil --> Exception

     (lst: @unchecked) match {
         case head :: tail => ...
     }