Showing posts with label inheritance. Show all posts
Showing posts with label inheritance. 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.

Monday, February 15, 2010

Self Annotation vs inheritance

At first glance the "sself annotation" declaration seems similar to extending another class. (For a look at self annotations read the topic: Self Type.) They are completely different but the comparison is understandable since both of them provide access to the functionality of referenced class.

For example both of the following compile:
  1. class Base {
  2.   def magic = "bibbity bobbity boo!!"
  3. }
  4. trait Extender extends Base {
  5.   def myMethod = "I can "+magic
  6. }
  7. trait SelfTyper {
  8.   self : Base => 
  9.   
  10.   def myMethod = "I can "+magic
  11. }

But the two are completely different. Extender can be mixed in with any class and adds both the "magic" and "myMethod" to the class it is mixed with. SelfType can only be mixed in with a class that extends Base and SelfTyper only adds the method "myMethod" NOT "magic".

Why is the "self annotations" useful? Because it allows several provides a way of declaring dependencies. One can think of the self annotation declaration as the phrase "I am useable with" or "I require a".

The following example demonstrates one possible reason to use self annotations instead of extend.

Note: These examples can be pasted into the REPL but I have shown that here because it would make the examples too long.
  1. import java.io._
  2. import java.util.{Properties => JProps}
  3. trait Properties {
  4.   def apply(key:String) : String
  5. }
  6. trait XmlProperties extends Properties {
  7.   import scala.xml._
  8.   
  9.   def xml(key:String) = Elem(null,key,Null,TopScope, Text(apply(key)))
  10. }
  11. trait JSonProperties extends Properties {
  12.   def json(key:String) : String = "%s : %s".format(key, apply(key))
  13. }
  14. trait StreamProperties {
  15.   self : Properties => 
  16.   
  17.   protected def source : InputStream
  18.   
  19.   private val props = new JProps()
  20.   props.load(source)
  21.   
  22.   def apply(key:String) = props.get(key).asInstanceOf[String]
  23. }
  24. trait MapProperties {
  25.   self : Properties =>
  26.   
  27.   protected def source : Map[String,String]
  28.   def apply(key:String) = source.apply(key)
  29. }
  30. val sampleMap = Map("1" -> "one""2" -> "two""3" -> "three")
  31. val sampleData = """1=one
  32.                     2=two
  33.                     3=three"""
  34. val sXml = new XmlProperties() with StreamProperties{
  35.               def source = new ByteArrayInputStream(sampleData.getBytes)
  36.            }
  37. val mXml = new XmlProperties() with MapProperties{
  38.               def source = sampleMap
  39.            }
  40. val sJSon = new JSonProperties() with StreamProperties{
  41.               def source = new ByteArrayInputStream(sampleData.getBytes)
  42.             }
  43. val mJSon = new JSonProperties() with MapProperties{
  44.               def source = sampleMap
  45.             }
  46. sXml.xml("1")
  47. mXml.xml("2")
  48. sJSon.json("1")
  49. mJSon.json("2")

The justification for using self annotations here is flexibility. A couple other solutions would be
  1. Use subclassing - this is poor solution because there would be an explosion of classes. Instead of having 5 traits you would need 7 traits. Properties, XmlProperties, JSonProperties, XmlStreamProperties, XmlMapProperties, JsonStreamProperties and JsonMapProperties. And if you later wanted to add a new type of properties or a new source like reading from a database then you need 2 new subclasses.
  2. Composition - Another strategy is to use construct the XmlProperties with a strategy that reads from the source. This is essentially the same mechanism except that you need to build and maintain the the dependencies. It also makes layering more difficult. For example:
    1. trait IterableXmlProperties {                                            
    2.     self : MapProperties with XmlProperties => 
    3.     def xmlIterable = source.keySet map {xml _}
    4.   }
    5.   
    6.   new XmlProperties with MapProperties with IterableXmlProperties {def source = sampleMap}

The next question that comes to mind is why use extends then if self annotation is so flexible? My answer (and I welcome discussion on this point) has three points.
  1. The first is of semantics and modeling. When designing a model it is often more logical to use inheritance because of the semantics that comes with inheriting from another object.
  2. Another argument is pragmatism.
    Imagine the collections library where there is no inheritance. If you wanted a map with Iterable functionality you would have to always declare Traversable with Iterable with Map (and this is greatly simplified). That declaration would have to be used for virtually all methods that require both the Iterable and Map functionality. To say that is impractical is an understatement. Also the semantics of Map is changed from what it is now. The trait Map currently includes the concept of Iterable.
  3. The last point is sealed traits/classes. When a trait is "sealed" all of its subclasses are declared within the same file and that makes the set of subclasses finite which allows certain compiler checks. This (as far as I know) cannot be done with self annotations.

Thursday, August 13, 2009

Traits and inheritance

Scala provides two structures for inheritance. Classes (abstract or not) and traits. Traits are very similar to Ruby Mixins meaning that they can contain code like abstract classes but like interfaces multiple traits can be inherited from.
Like interfaces traits cannot have constructors but in Scala variables can be abstract and therefore provide an easy way to simulate a constructor.
There are no method resolution conflicts because method definitions are always resolved right to left:
  1. class X extends Y with A with B with C

If ABC and Y all have the method (doit) the method in C will be used. If C calls super.doit that will call B.doit and so on.
Note: If Y defines doit the A, B, and C must define doit with the override keyword:
  1. override def doit() = {...}

In the above example ABC must be traits but Y can be a class or a trait. When inheriting you must always have one extends keyword which can optionally followed by one or more with clauses.

  1. scala> abstract class Animal {
  2.      |  val legs:Int
  3.      | val noise:String
  4.      | def makeNoise() = println(noise)
  5.      | }
  6. defined class Animal
  7. scala>  trait Quadriped {
  8.      | self:Animal =>
  9.      | val legs = 4
  10.      | }
  11. defined trait Quadriped
  12. scala> trait Biped {
  13.      | self:Animal =>
  14.      | val legs = 2
  15.      | }
  16. defined trait Biped
  17. scala> class Dog extends Animal with Quadriped {
  18.      | val noise = "Woof"
  19.      | override def makeNoise() = println( noise+" "+noise)
  20.      | }
  21. defined class Dog
  22. scala> new Dog().makeNoise()
  23. Woof Woof
  24. scala> abstract class GenericAnimal extends Animal{ 
  25.      | val noise = "glup"                          
  26.      | }
  27. defined class GenericAnimal
  28. scala> val quad = new GenericAnimal() with Quadriped
  29. quad: GenericAnimal with Quadriped = $anon$1@10bfb545
  30. scala> quad.makeNoise()
  31. glup
  32. scala> val biped = new GenericAnimal() with Biped
  33. biped: GenericAnimal with Biped = $anon$1@7669521
  34. scala> val biped = new GenericAnimal() with Biped{
  35.      | override val noise = "Hello"
  36.      | }
  37. biped: GenericAnimal with Biped = $anon$1@6366ce5f
  38. scala> biped.makeNoise()
  39. Hello