I'd like more clarity in understanding the place Tuple2 class in Scala 3.3.1.
In its source code, what is clear is that this class inherits the Product2 trait. What remains doubt to me is how it comes into being that Tuple2 also inherits immediately from:
I haven't been able understood that.
I've read through relevant Scala doc and source code. I can understand the overall design idea of inheritance of these hierarchy but I haven't understood the more nuanced points (the question as an example)
Let's not confuse classes and types, inheritance (subclassing) and subtyping
What is the difference between Type and Class?
What is the difference between a class and a type in Scala (and Java)?
https://typelevel.org/blog/2017/02/13/more-types-than-classes.html
What is the difference between subtyping and inheritance in OO programming?
https://counterexamples.org/subtyping-vs-inheritance.html
From OOP point of view, Tuple2
extends (is a subclass of) Product2
, Product2
extends Product
. And *:
extends NonEmptyTuple
, NonEmptyTuple
and EmptyTuple
extend Tuple
. Two independent OOP hierarchies.
Making A *: B *: EmptyTuple
a subtype of Tuple2[A, B]
and vice versa (and providing syntax sugar (A, B)
) is a compiler magic. According to the spec,
A tuple type
(T1,...,Tn)
wheren ≥ 2
is sugar for the typeT1 *: ... *: Tn *: scala.EmptyTuple
, which is itself a series of nested infix types which are sugar for*:[T1, *:[T2, ... *:[Tn, scala.EmptyTuple]]]
.
https://scala-lang.org/files/archive/spec/3.4/03-types.html#tuple-types
The conformance relation
(<:)
is the smallest relation such thatS <: T
is true if any of the following conditions hold.
- ...
T = scala.Tuple_n[T1,...,Tn]
with1 ≤ n ≤ 22
, andS <: T1 *: ... *: Tn *: scala.EmptyTuple
.
https://scala-lang.org/files/archive/spec/3.4/03-types.html#conformance
In the compiler, subtyping is implemented in TypeComparer
/** Does `tycon` have a field with type `tparam`? Special cased for `scala.*:`
* as that type is artificially added to tuples. */
private def typeparamCorrespondsToField(tycon: Type, tparam: TypeParamInfo): Boolean = ...
/** Subtype test for the hk application `tp2 = tycon2[args2]`.
*/
def compareAppliedType2(tp2: AppliedType, tycon2: Type, args2: List[Type]): Boolean = {
...
case tycon2: TypeRef =>
...
tycon2.info match {
...
case info2: ClassInfo =>
tycon2.name.startsWith("Tuple") &&
defn.isTupleNType(tp2) && recur(tp1, tp2.toNestedPairs) ||
tryBaseType(info2.cls)
https://dotty.epfl.ch/docs/internals/type-system.html#subtyping-checks-1
https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Regarding Serializable
, all case classes extend Product
and Serializable
and Tuple2
is a case class. You can switch on scalacOptions ++= Seq("-Vprint:typer", "-Xprint-types"
) and see
case class MyClass(i: Int, s: String)
// case class MyClass(i: Int, s: String) extends
// <<<new Object:Object>:((): Object)>():Object>, Product, Serializable {
// ...
Here is explanation of generating sugar for case classes in Scala 2 but in Scala 3 this should be similar
(so one can find a specific place where extends Serializable
is added).