Thursday, February 14, 2013

Scala: talking about yourself

TL;DR - trait T[A <: T[A]] { self: A => }

In a Scala trait, how can one refer to "The type that is implementing this trait"?
(to the tune of Jingle Bells) [1]
Swishing to and fro, amoebas like to play
In a petri dish, dividing once a day
I thought that there were two, nothing less or more
But I looked in a microscope and counted up to four!
Oh, single cells, single cells, see how they divide
There was on Christmas Day but now it's multiplied! 
Oh, single cells, single cells, nothing much to do
But swim in a petri dish dividing into two!

Say there's a trait for these amoebas: they can divide into two of themselves. What would that divide method return? A pair of itself. How can it say that?

trait AsexualReproduction {
   def divide: (?, ?)
}

What I really want to say is "A pair of whatever I am." But I don't have enough information about that type. If the concrete class is A, that's what I'd like to return. Like, a self type.

trait AsexualReproduction { self: A =>
   def divide: (A, A)
}

Unfortunately, type A here is undefined. So, let's make it a type parameter.

trait AsexualReproduction[A] { self: A =>
   def divide: (
AA)
}

This works, but it doesn't force people get the type parameter right - a class could extend AsexualReproduction[Any] and divide into whatever it liked. We have to restrict the type parameter to be the type that's implementing AsexualReproduction - specifically, implementing AsexualReproduction of itself:
trait AsexualReproduction[<: AsexualReproduction[A]] { self: A =>
   def divide: (AA)
}


class Amoeba extends AsexualReproduction[Amoeba] {
   def divide = (new Amoeba, new Amoeba)
}


This isn't pretty. It's the best we've come up with so far to give a trait information about its implementor, so that the trait can express return values in terms of the implementing type.
Nothing about this says that A has to be a concrete class, so one could extend Amoeba with a SpikyAmoeba that divides into SquishyAmoebas. It'd be better if I could require that A be declared final.

If you have a better way to express this, I'm open to ideas!

Update: here's one way to do this with abstract types instead.

----------------------------------
[1] Thanks to Mr. Ault, my high school chemistry teacher.