How Assigning TupleN value to *: type variable works in Scala 3?

112 Views Asked by At

From the type hierarchy tree in scala

  • let a:Tuple2[Int,Int], I know Tuple2[Int,Int] extends from Product2[Int,Int];
  • let b:1 *: 2 *: EmptyTuple has type Tuple (refined as Int *: Int *: EmptyTuple)

They are different types and don't have any parent relation. The only thing they both have is Product, they both extend from Product.

But I can assign a to b and opposite, why?

2

There are 2 best solutions below

1
Michael Zajac On BEST ANSWER

In Scala 3, the Tuple1...Tuple22 types are synthetically modified by the compiler to extend the *: types. That is, Tuple2[A, B] is modified to extend A *: B *: EmptyTuple (which extends Tuple).

Therefore, you can assign a Tuple2[Int, Int] to a Int *: Int *: EmptyTuple. Likewise, the reverse is possible because a A *: ... EmptyTuple will be treated like a TupleN whenever it can (<= 22 type arguments).

2
yangzai On

They are different types but that does not mean that they are unrelated. 1 *: 2 *: EmptyTuple is a subtype of Tuple2[Int,Int] because the singleton literals 1 and 2 are subtypes of Int and the type params of Tuple2 are covariant.

You can widen an instance of 1 *: 2 *: EmptyTuple to Tuple2[Int,Int] but not the other way around.

Please note that Tuple2[A, B] and A *: B *: EmptyTuple are equivalent. 1 *: 2 *: EmptyTuple is assumed to a type-level expression based on question's syntax, but if it were a value-level expression then the type of it would be Int *: Int *: EmptyTuple which would be equivalent to Tuple2[Int,Int]. In that case instance of both types would be "assignable" to one another.

Code for reference:

@main def main() =
  type T1 = Tuple2[Int, Int]
  type T2 = 1 *: 2 *: EmptyTuple

  val t1a: T1 = (1, 2) //compiles
  val t2a: T2 = (1, 2) //compiles
  val t1b: T1 = (2, 1) //compiles
//  val t2b: T2 = (2, 1) // does not compile

//  t1a: T2 //does not compile
  t2a: T1 //compiles

  summon[1 <:< Int] // compiles
//  summon[1 =:= Int] // does not compile
  summon[T2 <:< T1] //compiles
//  summon[T1 <:< T2] // does not compile
//  summon[T1 =:= T2] // does not compile

  //these are the same types, not exclusively subtypes of one another
  summon[Tuple2[Int, Int] =:= Int *: Int *: EmptyTuple]
  summon[Tuple2[Int, Int] <:< Int *: Int *: EmptyTuple]
  summon[Int *: Int *: EmptyTuple <:< Tuple2[Int, Int]]