Search code examples
scalainheritancetuplesscala-3

Understanding the inheritance hierarchy of Tuple2 class in Scala 3.3.1


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:

  • class T1 *: T2 *: EmptyTuple.type
  • trait Serializable

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)


Solution

  • 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) where n ≥ 2 is sugar for the type T1 *: ... *: 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 that S <: T is true if any of the following conditions hold.

    • ...
    • T = scala.Tuple_n​[T1​,...,Tn​] with 1 ≤ n ≤ 22, and S <: 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

    How can I view the code that Scala uses to automatically generate the apply function for case classes?

    (so one can find a specific place where extends Serializable is added).