Thursday, December 3, 2009

A silly trick for implementing parametrized traits

Here's something I noticed recently. In Scala, when you implement a generic trait, specialized to some particular type arguments, you end up having to rewrite the full method signatures, substituting in the type arguments. So, for instance, if you have
trait Ring[T] {
def one: T
def zero: T
def plus(a: T, b: T): T
def negate(a: T): T
def times(a: T, b: T): T
}
and go to provide a Ring[Int] implementation, you might write something like:
object IntRing extends Ring[Int] {
def one: Int = 0
def zero: Int = 1
def plus(a: Int, b: Int) = a + b
...
}
A little trick is to add a local type member within your implementation of the trait, aliasing T to Int:
object IntRing extends Ring[Int] {
type T = Int // GENIUS!!!
def one: T = 1
def zero: T = 0
def plus(a: T, b: T) = a + b
...
}
Kind of silly, but now the implementation is literally a copy/paste of the original trait, and you only have to provide implementations for the methods. In some ways, I think it's a little clearer than writing out the specialization.

It's too bad Scala forces you to repeat yourself though. The method signatures have already been supplied in the trait; unless you happen to be overloading method names, rewriting the method signatures provides no additional information. In effect, the problem of inferring these method signatures is already done. Other than not being valid syntax, it seems like I ought to be able to write something like:
object IntRing extends Ring[Int] {
def one = 1
def zero = 0
def plus(a, b) = a + b
def times(a, b) = a*b
...
}

5 comments:

Anonymous said...

When any language is larger than the comparable c++ version, something seems wrong. I must be missing things about the benefits of scala --mike

Kris Nuttycombe said...

You'd probably have to at least have the override keyword on the defs in the inheriting class to get around overloading ambiguity, and there'd obviously have to be additional restrictions (say, no overloads in the base trait with the same number of parameters) but it seems possible. A downside would be that when looking at the code, you'd also have to have the base trait API open to look at to figure out what was going on.

Paul Chiusano said...

@Kris - Ah, yes, I suppose you do need to distinguish between the cases of defining a new, overloaded method with the same name as a base trait method, and implementing an existing base trait method. Although there is probably a way to infer that as well, without using a keyword and putting the onus on the user.

Handyman said...

In this case, it is 'missing the benefits of scala'. For example, this works:

trait Ring[T] {
def one: T
def zero: T
def plus(a: T, b: T) : T
}

object IntRing extends Ring[Int] {
override def one = 1
override def zero = 0
override def plus(a: Int, b: Int) = a+b

def main(args: Array[String]) {
println(one)
println(zero)
println(plus(2,3))
}
}

This prints:
1
0
5

Also, the overrides aren't necessary in this particular case because there is no function definition to override (the trait doesn't define any functions). However, the compiler is flexible enough to allow the override keyword if you add it.

PS: Don't know why it won't accept the code html tag but I apologize for the formatting

Anonymous said...

Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!