Showing posts with label with. Show all posts
Showing posts with label with. Show all posts

Monday, March 1, 2010

NullPointer when mixed traits (Warning)

This tip is mainly to document a 'GOTCHA' that I got caught by recently. It basically goes like this:

Trait Y extends(or has self-type) X. Trait X defines some abstract method 'm'. The initialization code in Y accesses 'm'. Creation of an object new X with Y results in: *Boom* NullPointerException (on object creation).

The example in code:
  1. scala> trait X { val x : java.io.File }
  2. defined trait X
  3. scala> trait Y {self : X => ; val y = x.getName} 
  4. defined trait Y
  5. scala> new X with Y { val x = new java.io.File("hi")}
  6. java.lang.NullPointerException
  7. at Y$class.$init$(< console>:5)
  8. at $anon$1.< init>(< console>:7)
  9. ...

At a glance it seems that x should override the abstract value x in trait X. However the order in which traits are declared is important. In this case first Y is configured then X. Since X is not yet configured Y throws an exception. There are several ways to work around this.
Option 1:
  1. trait X {val x : java.io.File}
  2. trait Y {self : X => ; val y = x.getName}
  3. /*
  4. Declaring Y with X will work because Y is initialized after X
  5. but remember that there may
  6. be other reasons that X with Y is required.  
  7. Method resolution is one such reason
  8. */
  9. new Y with X { val x = new java.io.File("hi")}

Option 2:
  1. trait X { val x : java.io.File }
  2. trait Y {self : X => ; def y = x.getName}
  3. /*
  4. Since method y is a 'def' x.getName will not be executed during initialization.
  5. */
  6. scala> new X with Y { val x = new java.io.File("hi")}
  7. res10: java.lang.Object with X with Y = $anon$1@7cb9e9a3

Option 3:
  1. trait X { val x : java.io.File }
  2. trait Y {self : X => ; lazy val y = x.getName}
  3. /*
  4. 'lazy val' works for the same reason 'def' works: x.getName is not invoked during initialization
  5. */
  6. scala> new X with Y { val x = new java.io.File("hi")}
  7. res10: java.lang.Object with X with Y = $anon$1@7cb9e9a3

Option 4:
  1. trait X {val x : java.io.File }
  2. trait Y extends X {def y = x.getName}
  3. /*
  4. if Y extends X then a new Y can be instantiated
  5. */
  6. new Y {val x = new java.io.File("hi")}

Two more warnings. First, the same error will occur whether 'x' is a def or a val or a var.
  1. trait X { def x : java.io.File }   
  2. trait Y {self : X => ; val y = x.getName}     
  3. new X with Y { val x = new java.io.File("hi")}

Second warning: In complex domain models it is easy to have a case where Y extends X but the final object is created as: new X with Y{...}.

You will get the same error here because (I think) the compiler recognized that Y is being mixed in with X and therefore the X will be initialized as after Y instead of before Y.

First the code:
  1. trait X { def x : java.io.File }   
  2. trait Y extends X { val y = x.getName}        
  3. new X with Y { val x = new java.io.File("hi")}

If the code instantiated new Y{...} the initialization would be X then Y. Because X can only be initialized once, the explicit declaration of new X with Y forces Y to be initialized before X. (X can only be initialized once even when it appears twice in the hierarchy).

This is a topic called linearization and will be addressed in the future.

Thursday, October 1, 2009

Multiple type bounds on a generic parameter

Suppose you want to declare a method to take objects that implement two interfaces or parent objects. For example suppose the parameter must implement both Iterable and a function so that you can access the elements of the iterable via object(index). How can you do that in scala?

This topic is derived from How do I setup multiple type bounds in Scala?

Answer:
  1. scala>def process[R <: Function1[Int,String]with Iterable[String]] (resource:R) = {
  2.      | println("using function:"+resource(0))
  3.      | println("using iterable:"+resource.elements.next)
  4.      | }
  5. process: [R <: (Int) => String with Iterable[String]](R)Unit
  6. // Array is a Function1 and iterable so this works
  7. scala> process (Array("1","2","3"))                                                  
  8. using function:1
  9. using iterable:1

Thursday, September 3, 2009

Adding methods using Traits

One useful application of a trait is the case where you want to add functionality to an existing class. In this example I have a class provided by a third party library (in this just a simple StringReader class from the Java library). But I want to be able to read lines as well as use the standard read methods.

One solution is to create a trait and when I instantiate the StringReader mix in the new trait. Code like new StringReader() with Lines results in a new class that extends StringReader and the trait Lines. As a result we have all the methods of StringReader and Lines. The biggest benefit is that we can define the trait to work with any Reader and then when we create the real instance we can mix it in to any class that extends Reader.

The other solution that can be used is to create an implicit conversion from StringReader to some other class. There are two draw backs here:
  • It is harder to tell what is happening
  • A trait can contain state but a "view" (which is what you get when one class is implicitly converted to another class) has no state it is just a view of the original class. In this example a view would work but in other examples like creating a pushback reader, it would not work.

Here is a simple example:

  1. scala>trait Lines {
  2.      | // the self type declares what type of class the trait can be applied to
  3.      | // if there is no self type then it is assumed it can be applied to Any type
  4.      | self:java.io.Reader =>
  5.      | def nextLine:Option[String] = {
  6.      | val builder = new scala.collection.mutable.StringBuilder()
  7.      |
  8.      | var next = read()
  9.      |
  10.      | while( next != -1 && next.toByte.toChar != '\n' ){
  11.      | builder += next.toByte.toChar
  12.      | next = read()
  13.      | }
  14.      |
  15.      | if( builder.isEmpty ) None
  16.      | else Some(builder.toString)
  17.      | }
  18.      | }
  19. defined trait Lines
  20. // Strings starting and ending with (""") are called raw strings.  All characters 
  21. // within """ """ are automatically escaped.
  22. // I am creating a reader and mixing in the Lines trait on construction
  23. scala>val reader = new java.io.StringReader( """line one
  24.      | line two"""with Lines
  25. reader: java.io.StringReader with Lines = $anon$1@3850620f
  26. scala> reader.nextLine
  27. res0: Option[String] = Some(line one)
  28. scala> reader.nextLine
  29. res1: Option[String] = Some(line two)
  30. scala> reader.nextLine
  31. res2: Option[String] = None
  32. scala> reader.nextLine
  33. res3: Option[String] = None
  34. // we can define a method that takes a reader with lines
  35. scala>def toCollection( reader:java.io.StringReader with Lines) = {
  36.      | def collect:List[String] = reader.nextLine match {
  37.      |   case None => Nil
  38.      |    // we do not need to worry about stack overflow
  39.      |    // because of tail recursion.  This method cannot be
  40.      |    // extended and collect is the last like in the collect
  41.      |    // method so this method will be transformed into a loop
  42.      |   case Some( line ) => line :: collect
  43.      | }
  44.      |
  45.      | collect
  46.      | }
  47. toCollection: (reader: java.io.StringReader with Lines)List[String]
  48. scala> toCollection( new java.io.StringReader( "line one\nlinetwo" ) with Lines).size
  49. res8: Int = 2