I would like to have a function like:
def useXX[T <: Tuple, X <: XX[T]](a:X, b:X): X = a
in Dotty/Scala 3 that ensures that the singleton types of XX[T]
in the function parameters a
and b
are the same. More concretely, the following should fail to compile:
val s3 = XX.xy((1,2,3))
val s4 = XX.xy((1,2,4))
val sr3 = useXX(s3, s4) // Error, should fail
Note that in general both s3
and s4
are of the type (Int, Int, Int)
, so compilation is successful. However the singleton types (1, 2, 3)
and (1, 2, 4)
do not match because 3.type
and 4.type
are not the same.
Now what do I mean by automate? If we declare the singletons explicitly:
val s1: XY[(1,2,3)] = XX.xy((1,2,3))
val s5: XY[(1,2,4)] = XX.xy((1,2,4))
val sr3 = useXX(s1, s5) // Ok, fails
then compilation fails. The problem is that now we depend on the programmer to declare these types explicitly. So we loose the advantage of typing. I think this an unsolved issue. I have tried using the with Singleton
to no avail.
So my question is, is there any way I can force compilation failure, even if it means demanding the programmer to explicitly provide the Singleton type?
TIA
If anyone wants to experiment, the code below can be found here:
sealed trait XX[T<:Tuple](val s:Array[Int])
object XX:
sealed case class XY[T<:Tuple] private[XX] (override val s:Array[Int]) extends XX[T](s)
def xy[T<:Tuple](t:T): XY[T] =
val l = t.toArray.map(_.asInstanceOf[Int])
new XY(l)
import XX._
val t: (1,2,3) = (1,2,3)
val s0: XY[(1,2,3)] = XX.xy[(1,2,3)]((1,2,3))
val s1: XY[(1,2,3)] = XX.xy((1,2,3))
val s2 = XX.xy((1,2,4,5))
val s3 = XX.xy((1,2,3))
val s4 = XX.xy((1,2,4))
val s5: XY[(1,2,4)] = XX.xy((1,2,4))
// We need to explicitly use Singleton types
def useXX[T <: Tuple, X <: XX[T]](a:X, b:X): X = a
val sr0 = useXX(s0, s1)
//val sr1 = useXX(s0, s2) // Ok, fails
//val sr2 = useXX(s2, s3) // Ok, fails
val sr3 = useXX(s3, s4) // Error, should fail
//val sr3 = useXX(s1, s5) // Ok, fails
@main
def run: Unit =
// Nothing to do
println("Done")
()