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?
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")