I’m trying to create types Tuple equivalent to the ones in the Scala library, only with a :+ method that extends a Tuple into a Tuple by addition of the N+1st value — so that I will be able to construct Tuples recursively:
class Test {
abstract class Tuple {
//protected type Next[_] <: Tuple
//def :+[T](p: T): Next[T]
}
case class Tuple0() extends Tuple {
protected type Next[T] = Tuple1[T]
def :+[T](p: T): Next[T] = Tuple1(p)
}
case class Tuple1[+T1](p1: T1) extends Tuple {
protected type Next[T] = Tuple2[T1, T]
def :+[T](p: T): Next[T] = Tuple2(p1, p)
}
case class Tuple2[+T1, +T2](p1: T1, p2: T2) extends Tuple {
protected type Next[-T] = Nothing
def :+[T](p: T): Next[T] = throw new IndexOutOfBoundsException();
}
}
This compiles, but as soon as I uncomment the definition of Tuple#Next, I get:
Test.scala:13: error: covariant type T1 occurs in invariant position in type [T]Test.this.Tuple2[T1,T] of type Next
protected type Next[T] = Tuple2[T1, T]
^
one error found
Why is that? Can you provide a workaround which would allow me to build Tuples (of mixed, type-safe value types) recursively?
Thanks.
You could do what Mark Harrah does in up:
That is, don’t have a type member for the next type. There may be things you can’t do like
this… you’ll notice that up’s HList is not covariant for this reason.
I would really like it if someone could point out a general way to make type
members covariant. I’m afraid the reason why they are not is above my head, though it might
have something to do with this sentence from Martin Oderksy’s paper:
Though if someone could explain that sentence to me I would be delighted 😉
Edit: Here is another approach that is closer to what you originally asked for. On
writing it I realized I’m not sure if this will really do what you want…
maybe you could give an example of how you’re intending to use these tuples?
Since we can’t have covariant type members, we can put the “next tuple” logic
into a separate trait:
And then implicitly convert to it:
This is a general technique I have found useful: Scala doesn’t complain if you
implicitly convert a covariant type to an invariant type.
This then allows you to do 2 things above what you could do with regular tuples:
1) Build a tuple manually in steps, and preserve type information:
2) Build a tuple dynamically and, sadly, lose type information:
What we really would have liked to do
would be to have written
That is, ensure that after adding 1 element, the result can then have more
things added to it; otherwise we could not build tuples dynamically.
Unfortunately, Scala does not accept view bounds on type members. Luckily, a
view bound is nothing more than a method that does the conversion; so all we
have to do is manually specify the method; hence
nextAdd.This may not be what you are looking for, but maybe it will give you some ideas
how to get closer to your actual goal.