Answer for the simplified example
(answer for the first example below)
It looks like you don't care about the precise type parameter of the X[_]
in case class Y(xs: X[_]*)
, as long as they are all the same. You just want to prevent users to create Y
s that don't respect this.
One way to achive this would be to make the default Y
constructor private:
case class Y private (xs: Seq[X[_]])
// ^^^^^^^ makes the default constructor private to Y, xs is still public
// Note also that xs is now a Seq, we will recover the repeated arg list below.
and define your own constructor this way:
object Y {
def apply[B](): Y = Y(Nil)
def apply[B](x0: X[B], xs: X[B]*): Y = Y(x0 +: xs)
// Note that this is equivalent to
// def apply[B](xs: X[B]*): Y = Y(xs)
// but the latter conflicts with the default (now private) constructor
}
Now one can write
Y()
Y(X("a"))
Y(X(1), X(1), X(5), X(6))
Y[Int](X(1), X(1), X(5), X(6))
and the following doesn't compile:
Y(X(1), X("1"))
Answer to the first example
We make the constructor private and change the repeated arg list to a Seq as above:
case class Rect2D[A] private (rows: Seq[Sized[Seq[A], _]])
// ^^^^^^^ ^^^^ ^
Let's define our own constructor(s):
object Rect2D {
def apply[A](): Rect2D[A] = Rect2D[A](Nil)
def apply[A,N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs)
}
Now the following compiles:
val r0: Rect2D[_] = Rect2D()
val r: Rect2D[Int] = Rect2D[Int]()
val r1: Rect2D[Int] = Rect2D(Sized[Seq](1, 2))
val r2: Rect2D[Int] = Rect2D(Sized[Seq](1, 2), Sized[Seq](2, 3))
val r3: Rect2D[Int] = Rect2D(Sized[Seq](1, 2), Sized[Seq](2, 3), Sized[Seq](2, 3), Sized[Seq](2, 3))
val r4: Rect2D[Any] = Rect2D(Sized[Seq](1, 2), Sized[Seq]("a", "b"), Sized[Seq](2, 3), Sized[Seq](2, 3)) // Works because both Sized and Seq are covariant
// Types added as a check, they can be removed
and the following doesn't:
val r5 = Rect2D(Sized[Seq](1, 2), Sized[Seq](1, 2, 3))
One drawback is that one cannot write something like
val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))
// ^^^^^
one has to write this instead
val r2 = Rect2D[Int, Nat._2](Sized[Seq](1, 2), Sized[Seq](2, 3))
// ^^^^^^^^
Let's fix this!
Enhanced solution for the first example
A cleaner solution would be define the constructors above this way:
object Rect2D {
def apply[A,N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs) // Same as above
case class Rect2DBuilder[A]() {
def apply(): Rect2D[A] = Rect2D[A](Nil)
def apply[N <: Nat](r0: Sized[Seq[A], N], rs: Sized[Seq[A], N]*): Rect2D[A] = Rect2D[A](r0 +: rs)
}
def apply[A] = new Rect2DBuilder[A]
}
Now we can also write
val r2 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq](2, 3))
and the following would not compile
val r4 = Rect2D[Int](Sized[Seq](1, 2), Sized[Seq]("a", "b"), Sized[Seq](2, 3), Sized[Seq](2, 3))
// ^^^^^ ^^^^^^^^