March 17, 2010

Using abstract type fields instead of parameterised types in Scala

I've been continuing my exploration of Scala by writing some code to do multi-dimensional measure calculations (something with which I'm quite familiar after about 5 years of doing it "as a day job" in the past, but which I've never been quite happy with a representation for). This can be found at http://code.google.com/p/lascala if you're interested, and any comments (especially on making it more idiomatic in its use of the language) would be much appreciated.

However, the main point of the post was to note that I've found, a little to my surprise, that when building type hierarchies using abstract type fields is often cleaner (less verbose, more DRY) than using parameterised types. When I first saw them mentioned I was sceptical about their usefulness, as I couldn't see why I'd prefer:

trait Base {
  type T
  ...
}

trait Super extends Base {
  type T = String
  ...
}

to:

trait Base[T] {
  ...
}

trait Super extends Base[String] {
  ...
}

And while in such a simple case there is no particular reason to, when building more complex hierarchies it can in fact save some repitition. For example:

trait Measure {
  type K
  type V
  def aggData: Iterable[(K, V)]
}

trait Measure1[U, T1] extends Measure {
  type V = U
  type D1 = T1
  type K = T1
}

trait Measure2[U, T1, T2] extends Measure {
  type V = U
  type D1 = T1
  type D2 = T2
  type K = (D1, D2)
}

trait PreAggregated extends Measure {
  def data: Iterable[(K, V)]
  def aggData = data
}

object ExampleMeasure1 extends Measure1[Int, String] with PreAggregated {
  data = ...
}

object ExampleMeasure2 extends Measure2[Int, String, String] with PreAggregated {
  data = ...
}

This is already appreciably less verbose than the following, especially in the use of the classes which is arguably more important than in their definitions:

trait Measure[K, V] {
  def aggData: Iterable[(K, V)]
}

trait Measure1[V, D1] extends Measure[D1, K]

trait Measure2[V, D1, D2] extends Measure[(D1, D2), K]

trait PreAggregated[K, V] extends Measure[K, V] {
  def data: Iterable[(K, V)]
  def aggData = data
}

object ExampleMeasure1 extends Measure1[Int, String] with PreAggregated[String, Int] {
  data = ...
}

object ExampleMeasure2 extends Measure2[Int, String, String] with PreAggregated[(String, String), Int] {
  data = ...
}

Some of the duplication could be avoided by having a series of PreAggregated traits mirroring the dimensional MeasureN ones, but this can get bloated very quickly, especially as it's likely that I'll need to support at least six dimensions. And repition also reappears when traits are stacked (e.g. to encapsulate storage and caching behaviours):

trait Measure[K, V] {
  def aggData:Iterable[(K, V)]
}

trait Measure1[V, D1] extends Measure[D1, K]

trait Measure2[V, D1, D2] extends Measure[(D1, D2), K]

trait PreAggregatedMeasure1[V, D1] extends Measure1[V, D1] {
  def data: Iterable[(D1, V)]
  def aggData = data
}

trait PreAggregatedMeasure2[V, D1, D2] extends Measure2[V, D1, D2] {
  def data: Iterable[((D1, D2), V)]
  def aggData = data
}

object ExampleMeasure1 extends PreAggregatedMeasure1[Int, String] {
  data = ...
}

object ExampleMeasure2 extends PreAggregatedMeasure2[Int, String, String] {
  data = ...
}

Comments?

1 comments:

Paul Bartlett said...

Update: a kind colleague has pointed out this - http://www.artima.com/weblogs/viewpost.jsp?thread=270195 - where Bill Venners covers the same topic (no doubt somewhat more thoroughly :) )