Suppose one wants to build a novel generic class, Novel[A]
. This class will contain lots of useful methods--perhaps it is a type of collection--and therefore you w
After discussions on the Scala mailing list--many thanks to the people there for setting me on the right track!--I think that this is the closest that one can come to a minimal framework. I leave it here for reference, and I'm using a different example because it highlights what is going on better:
abstract class Peano[A,MyType <: Peano[A,MyType]](a: A, f: A=>A) {
self: MyType =>
def newPeano(a: A, f: A=>A): MyType
def succ: MyType = newPeano(f(a),f)
def count(n: Int): MyType = {
if (n<1) this
else if (n==1) succ
else count(n-1).succ
}
def value = a
}
abstract class Peano2[A,MyType <: Peano2[A,MyType]](a: A, f: A=>A, g: A=>A) extends Peano[A,MyType](a,f) {
self: MyType =>
def newPeano2(a: A, f: A=>A, g: A=>A): MyType
def newPeano(a: A, f: A=>A): MyType = newPeano2(a,f,g)
def pred: MyType = newPeano2(g(a),f,g)
def uncount(n: Int): MyType = {
if (n < 1) this
else if (n==1) pred
else uncount(n-1).pred
}
}
The key here is the addition of the MyType
type parameter that is a placeholder for the type of the class that we'll really end up with. Each time we inherit, we have to redefine it as a type parameter, and we have add a constructor method that will create a new object of this type. If the constructor changes, we have to create a new constructor method.
Now when you want to create a class to actually use, you only have to fill in the constructor method with a call to new (and tell the class that it's of its own type):
class Peano2Impl[A](a: A, f: A=>A, g: A=>A) extends Peano2[A,Peano2Impl[A]](a,f,g) {
def newPeano2(a: A, f: A=>A, g: A=>A) = new Peano2Impl[A](a,f,g)
}
and you're off and running:
val p = new Peano2Impl(0L , (x:Long)=>x+1 , (y:Long)=>x-1)
scala> p.succ.value
res0: Long = 1
scala> p.pred.value
res1: Long = -1
scala> p.count(15).uncount(7).value
res2: Long = 8
So, to review, the minimal boilerplate--if you want to include recursive methods, which breaks the other style of answer--is for any methods that return a new copy from outside the class (using new or a factory or whatever) to be left abstract (here, I've boiled everything down to one method that duplicates the constructor), and you have to add the MyType
type annotation as shown. Then, at the final step, these new-copy methods have to be instantiated.
This strategy works fine for covariance in A
also, except that this particular example doesn't work since f
and g
are not covariant.