Search code examples
scalagenericssubclassinggeneric-programmingshapeless

Why does the signature of deriveHCons declare `HK <: Symbol` when Symbol is a final class


Here is the signature of deriveHCons in LabelledProductTypeClassCompanion of Shapeless:

implicit def deriveHCons[HK <: Symbol, HV, TKV <: HList]
    (implicit
      ch: Lazy[C[HV]],
      key: Witness.Aux[HK],
      ct: Lazy[Wrap[TKV] { type V <: HList }]
    ): Wrap.Aux[FieldType[HK, HV] :: TKV, HV :: ct.value.V] = ...

It seems strange to me that we declare a type parameter HK that must derive from Symbol when Symbol is a final class. How can anything but Symbol replace the type parameter HK? If HK is always Symbol, this signature would be less imposing if it got rid of HK and substituted Symbol in the type signature directly would it not?


Solution

  • I'm going to explain why this applies for Int, but it's actually the same for Symbol.

    Int is final right? Here is an instance of it:

    val n = 2
    

    But we can actually give n a more precise type than that. How? With literal singleton types:

    val n: Witness.`2`.T = 2
    

    Now n has type Witness.`2`.T, a.k.a. 2.type, or just type 2. 2 is the sole inhabitant of it, e.g. 3: Witness.`2`.T will not compile. And we have Witness.`2`.T <: Int

    The same thing applies to symbols: although Symbol is final, its values, the literal ones in particular, can be given more refined types, the ones that correspond to these values:

    val s = Symbol("s")
    val refined: Witness.`'s`.T = Symbol("s")