Friday, April 9, 2010

Elidable (remove method calls at compile time)

In Scala 2.8 there is a useful annotation called 'elidable'. This annotation flags a method so that given certain compiler flags all calls to the method will be removed. This is handy if you are writing a logger. The log methods can be annotated so that when compiling for production all log calls below a certain level would be removed from the compiled code. Several of the methods in Predef are annotated with elidable. Specifically, assume, assert and require.

The elidable annotation takes an Int parameter which specifies the priority of the method. The lower the integer the more likely the method would be removed during compilation. The elidable object defines several values that are used in our example.

When compiling with the -Xelide-below , the compiler parameter will remove all calls to elidable methods value and below.

To try the following example copy the example into a scala file (elidable.scala for example) and compile as indicated below:
  1. package example
  2. import scala.annotation.elidable
  3. import scala.annotation.elidable._
  4. object ElidableExamples {
  5.     @elidable(ALL) def all = println("all")
  6.     @elidable(ASSERTION) def assertion = println("assertion")
  7.     @elidable(CONFIG) def config = println("config")
  8.     @elidable(FINE) def fine = println("fine")
  9.     @elidable(FINER) def finer = println("finer")
  10.     @elidable(FINEST) def finest = println("finest")
  11.     @elidable(INFO) def info = println("info")
  12.     @elidable(OFF) def off = println("off")
  13.     @elidable(SEVERE) def severe = println("severe")
  14.     @elidable(WARNING) def warning = println("warning")
  15. }
  16. object Main extends Application {
  17. println("starting")
  18. import ElidableExamples._
  19. all
  20. assertion
  21. config
  22. fine
  23. finer
  24. finest
  25. info
  26. off
  27. severe
  28. warning
  29. println("ending")
  30. assert(false"boom!")
  31. }


Output from scalac elidable.scala && scala example.Main

starting
assertion
off
ending
java.lang.AssertionError: assertion failed: boom!
at scala.Predef$.assert(Predef.scala:93)
at example.Main$.(elidable.scala:34)
at example.Main$.(elidable.scala)
at example.Main.main(elidable.scala)

Output from scalac -Xelide-below 0 elidable.scala && scala example.Main

starting
assertion
config
fine
finer
finest
info
off
severe
warning
ending
java.lang.AssertionError: assertion failed: boom!
at scala.Predef$.assert(Predef.scala:93)
at example.Main$.(elidable.scala:34)
at example.Main$.(elidable.scala)
at example.Main.main(elidable.scala)

Output from scalac -Xelide-below 1000 elidable.scala && scala example.Main

starting
assertion
off
severe
ending
java.lang.AssertionError: assertion failed: boom!
at scala.Predef$.assert(Predef.scala:93)
at example.Main$.(elidable.scala:34)
at example.Main$.(elidable.scala)
at example.Main.main(elidable.scala)

Output from scalac -Xelide-below 3000 elidable.scala && scala example.Main

starting
off
ending

Tuesday, April 6, 2010

Scala 2.7 to 2.8 migration help

Scala 2.8 has some very significant differences from Scala 2.7 so if you want to migrate from Scala 2.7 to 2.8 you might want to view this Stack overflow topic:

What are the biggest differences between Scala 2.8 and Scala 2.7?

It has some good tips.

Variant Positions 3

The last few topics all discussed variance in its different forms. The following is a cheat sheet of the where the different variances exist within a class.

See Variant positions 1 for a discussion on one position in a class that is a contravariant position.

The example is:
  1. scala> class Output[+A] {def write(a : A) = () /*do write*/ }

In this example A in the method write is an contravariant position. Which means the previous definition is not legal because A is defined as Covariant in the class definition. In a class there are several positions with different variance characteristics. Here's the example from Programming in Scala:
  1. abstract class Cat[-T, +U] { 
  2.     def meow[W<sup>-</sup>](volume: T-, listener: Cat[U<sup>+</sup>, T<sup>-</sup>]-): Cat[Cat[U<sup>+</sup>, T<sup>-</sup>]-, U+]+
  3. }

If you remove the superscript + and - the above example actually compiles. The + and - indicate if the position is a covariant or contravariant position. Its not critical to memorize the positions (in my opinion). Just look it up as needed. The rule of thumb is that each nested position is inverted (flipped) value of it enclosing position.

That is all I will say about that :)

Sunday, April 4, 2010

Working on Scala IO

I just want to let everyone know that I am working hard on a Scala IO library right now and as such I want to dedicate all my spare time to doing that. So (as has been the case lately) I plan to only update the blog 2-3 times a week. Once done with the library I will try to increase the frequency of updates on the blog.


If you have any topics or code snippets you think would be fit good with the block please send them to me at jeichar.w@gmail.com.

Jesse

Wednesday, March 31, 2010

Variant Positions 2

This is a continuation of post: Variant Positions 1

...

My first attempt at the Verified was to make it a mutable object (my Java tourettes kicking in). But it cannot be covariant and mutable. Look at the code to see why:
  1. class Verified[+A <: V,V](assertion : (V) => Booleanprivate var value : A){
  2.     assert(assertion(value))
  3.     
  4.     def a = value
  5. // update is illegal.  See the example below
  6.     def update[ B >: A <: V](a : B) = value = a
  7. }
  8. def notNull(obj : AnyRef) = obj != null
  9. val v = new Verified(notNull, "hi")
  10. /*
  11. Up to this point everything looks ok but the next line
  12. will assign an object to value which is a reference to a String
  13. */
  14. update (new Object())

For Verified to be mutable A must be invariant. If you look at the Mutable collections in Scala they are all invariant.

Here is an interesting example of both invariant and covariant type parameters in a class hierarchy:
  1. scala> class X[+A](val x :A)
  2. defined class X
  3. scala> class Y[A](var a: A) extends X[A](a)
  4. defined class Y
  5. scala> val x: X[Any] = new Y[String]("hi")
  6. x: X[Any] = Y@1732a4df
  7. scala> x.asInstanceOf[Y[String]].a="ho"

This example is perfectly legal because no matter how X[Any] is used no illegal assignment in Y can occur. The interesting thing is that the object can be used in covariant usecases when only X is required. This is now the collections in Scala can work.

Here is a little example of collections invariance and covariance in action. In List the parameter is covariant but in Buffer it is invariant
  1. scala> def printList(l : List[Any]) = print(l mkString " :: ")
  2. printList: (l: List[Any])Unit
  3. scala> val buffer = Buffer(1,2,3)
  4. buffer: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3)
  5. scala> printList(buffer)
  6. 1 :: 2 :: 3
  7. /*
  8. ++ is part of Iterable.  Since Iterable is covariant ++ 
  9. returns a new buffer it does not modify the existing buffer
  10. All mutators are only defined on invariant traits
  11. */
  12. scala> buffer ++ List(4)
  13. res16: scala.collection.mutable.Buffer[Int] = ArrayBuffer(1, 2, 3, 4)
  14. scala> res16 eq buffer
  15. res17: Boolean = false
  16. /*
  17. buffer defines += to add an element to the buffer
  18. so res27 is the same buffer with an extra element
  19. */
  20. scala> buffer += 10
  21. res27: buffer.type = ArrayBuffer(1, 2, 3, 10)
  22. scala> res27 eq buffer
  23. res28: Boolean = true

Tuesday, March 30, 2010

Variant Positions 1

An additional topic on variance to finish up the major points on the topic. The previous two posts: contain required information for following this post.

In-, co- and contra- variance are the three types of variance expressible in Scala. I showed how this affects assignments and arguments being pass to methods in the last two topics. This looks at how the different types of variance influences how classes can be defined. In the last post we saw how the compiler complained about a method definition in a covariant class because the compiler recognized that such a definition was inherently dangerous and must be prohibited. The example was:
  1. scala> class Output[+A] {def write(a : A) = () /*do write*/ }
  2. < console>:5: error: covariant type A occurs in contravariant position in type A of value a
  3.        class Output[+A] {def write(a : A) = () /*do write*/ }
  4.                                    ^

For an class like Output it does not make sense to have A be covariant so we changed A to be contravariant. However suppose we have a collection type class.
  1. class Verified[+A] (assertion : (A) => Boolean, value : A){
  2.     assert(assertion(value))
  3.     
  4.     def a = value
  5.     def a_=(a : A) = new Verified(assertion, a)
  6. }

The previous definition is not legal because value and a in the parameter of a_= "occur in a contravariant position." What to do? Making A contravariant isn't an option:
  1. class Verified[+A <: V,V](assertion : (V) => Booleanval value : A){
  2.     assert(assertion(value))
  3. /*
  4. this is the key.  Restrict possible types of
  5. A Since B is a super (or equal) type of A
  6. */
  7.     def update[ B >: A <: V](a : B) = new Verified(assertion, a)
  8. }
  9. // example useage
  10. scala> def notNull(obj : AnyRef) = obj != null
  11. notNull: (obj: AnyRef)Boolean
  12. scala> val v = new Verified(notNull, "hi")
  13. v: Verified[java.lang.String,AnyRef] = Verified@307b37df
  14. scala> val newV = v update (new Object())
  15. newV: Verified[java.lang.Object,AnyRef] = Verified@36f72f09
  16. // 3 is not legal because the type parameter 'V' is AnyRef.  Int is a subclass of Any NOT AnyRef
  17. scala> val newV = v update (3)           
  18. < console>:8: error: inferred type arguments [Any] do not conform to method update's type parameter bounds [B >: java.lang.String <: AnyRef]
  19.        val newV = v update (3)
  20.                   ^

Saturday, March 27, 2010

Contravariance

Continuing on with variance and type parameters, this topic will discuss contravariance. See the post In- and Co- variance of type parameters for the intro material required for this topic.

Covariant parameters allow for an additional dimension of type compatibility:
  1. val l : List[Object] = List("this is legal")

Contravariance provides the opposite:
  1. // If the type parameter of list was contravariant this would be legal:
  2. val l : List[String] = List(new Object())

As covariance is indicated by a '+' before the type contravariance is indicated by a '-'
  1. scala> class X[-A]
  2. defined class X
  3. scala> val l : X[String] = new X[Object]
  4. l: X[String] = X@66201d6d

I can almost hear the "cool... but why?". Following the lead in the Programming In Scala book. Consider OutputStream first and a method in a Collection second. (The following code is illegal but consider it)
  1. class Output[+A] {def write(a : A) = () /*do write*/ }
  2. def writeObject(out : Output[Object]) = out.write("hello")
  3. /*
  4. Uh oh you this only is for outputting lists not Objects 
  5. (certainly not the String that is actually written)
  6. Runtime error for sure!
  7. */
  8. writeObject(new Output[List[String]])

The previous example (if it would compile) would explode because an Output that can only write lists is passed to the method. In the example a String is written to the Output object. The Output[List[String]] cannot handle that.

Fortunately the compiler sees the definition of the class and recognizes this is an error waiting to happen and makes it illegal:
  1. scala> class Output[+A] {def write(a : A) = () /*do write*/ }
  2. < console>:5: error: covariant type A occurs in contravariant position in type A of value a
  3.        class Output[+A] {def write(a : A) = () /*do write*/ }
  4.                                    ^

Consider the implications of making A contravariant?
  1. // The definition of object is now legal
  2. class Output[-A] {def write(a : A) = () /*do write*/ }
  3. // this is now a safe method definition since the parameter of Output must be a Object or a super class
  4. def writeObject(out : Output[Object]) = out.write("hello")
  5. // Now this is illegal as it should be
  6. scala> writeObject(new Output[List[String]])
  7. < console>:8: error: type mismatch;
  8.  found   : Output[List[String]]
  9.  required: Output[java.lang.Object]
  10.        writeObject(new Output[List[String]])
  11.        
  12. // this is legal... 
  13. scala> writeObject(new Output[Any])

In this example Output[Any] can be passed to the method. This makes sense. If the Output object knows how to write Any oject then it knows how to write an Object; its all good.