Thursday, June 11, 2009

Case Classes and Product

It's a well-known fact that by adding "case" before "class" in Scala, the compiler will generate some scaffolding for you like making all your paremeters "val", creating apply and unapply in a companion object. etc.

It's not a particular known fact that one of the things it does is extending the class with the trait Product. This trait makes it easy to access the elements of the class, like in the example below:

scala> trait recordType {
| self : Product =>
| def records : String = (
| for(parm <- self.productIterator)
| yield parm.asInstanceOf[AnyRef].getClass.getSimpleName+": "+parm
| ) mkString "\n"
| }
defined trait recordType

scala> case class X(i : Int, c : Char, s : String) extends recordType
defined class X

scala> val x = X(5, 'd', "this")
x: X = X(5,d,this)

scala> println(x.records)
Integer: 5
Character: d
String: this

Let me talk about the trait recordType a bit first. Its body first line declares that "self : Product =>". This has two purposes. First, it creates an alias for "this". Second, and most important, it tells the compiler that to be able to use this trait, a class must mix in the trait Product.

Anyway, the code works fine for simple classes, but it might be weird for collections:

scala> case class Y(a : Array[String], l : List[Int]) extends recordType
defined class Y

scala> val y = Y(Array("this", "example"), List(1,2,3))
y: Y = Y([Ljava.lang.String;@60a517,List(1, 2, 3))

scala> println(y.records)
String[]: [Ljava.lang.String;@60a517
$colon$colon: List(1, 2, 3)

Here, Array is mucked up because Java's Array are mucked up. As for the List, any non-empty List actually belongs to class "::", a subclass of List.

1 comment:

  1. Products are cool, and little known. But the sad thing is that they used to be even more powerful. A couple of Scala versions back, a case class with N parameters would extend ProductN[T1, T2, ..., Tn]. That mean one could trivialy have generic methods that took any case class (or tuple) with specific types.

    And, less trivially, one could use some implicits magic to have types ranging over arities of Products, which is kind of a poor man's version of dependent typing.

    But, alas, case class inheritance broke all that, and now only TupleN extends a ProductN trait. Sad...