Monday, May 17, 2010

Instance Type (Abstract type gotcha 1)

In a previous post about abstract types I showed one of the benefits of using abstract types over parameterized types. Abstract Types vs Parameter. The next several posts will feature potential problems you may encounter when using Abstract Types.

I should point out that abstract types are not inherently difficult to understand but they are rather different from anything you will see when you come from the Java world so if you are new to them I would use them with caution at first.

In the abstract types example you will notice that the abstract type 'I' in Foreach is not within the trait Source rather it is outside in the Foreach trait. At first one might consider putting the type in Source rather than Foreach. The naive change can get you in trouble (but there is a couple easy fixes)
  1. trait Foreach[A] {
  2.   trait Source {
  3.     type I <: java.io.Closeable  // moved this line into Source
  4.     def in : I
  5.     def next(in : I) : Option[A]
  6.   }
  7.   def source : Source
  8.   
  9.   def foreach[U](f : A => U) : Unit = {
  10.     val s = source.in
  11.     try {
  12.       def processNext : Unit = source.next(s) match {
  13.         case None => 
  14.           ()
  15.         case Some(value) => 
  16.           f(value)
  17.           processNext
  18.       }
  19.       
  20.       processNext
  21.     } finally {
  22.       // correctly handle exceptions
  23.       s.close
  24.     }
  25.   }
  26. }

Compiling the class results in a compilation error:

jeichar: tmp$ scalac XX.scala
XX.scala:12: error: type mismatch;
found : s.type (with underlying type Foreach.this.Source#I)
required: _2.I where val _2: Foreach.this.Source
def processNext : Unit = source.next(s) match {
^
XX.scala:16: error: type mismatch;
found : value.type (with underlying type Any)
required: A
f(value)
^
two errors found

So what is the problem? The problem is simple but subtle. Notice that source is defined as a def. So calling source 2 times may return 2 different instances of Source. A simple change can fix this. Either change def source : Source to val source : Source. Or change the method foreach to assign the result from source to a val.
  1. trait Foreach {
  2.   trait Source {
  3.     type I <: java.io.Closeable  // moved this line into Source
  4.     def in : I
  5.     def next(in : I) : Option[Int]
  6.   }
  7.   def source : Source
  8.   
  9.   def foreach[U](f : Int => U) : Unit = {
  10.     // this assignment allows this example to compile
  11.     val sameSource = source
  12.     val s = sameSource.in
  13.     try {
  14.       def processNext : Unit = sameSource.next(s) match {
  15.         case None => 
  16.           ()
  17.         case Some(value) => 
  18.           f(value)
  19.           processNext
  20.       }
  21.       
  22.       processNext
  23.     } finally {
  24.       // correctly handle exceptions
  25.       s.close
  26.     }
  27.   }
  28. }

No comments:

Post a Comment