Search code examples
scalageneric-programmingshapeless

LabelledGeneric instance generation on a tagged case class yields me an error in shapeless


I'm trying to get a LabelledGeneric instance from a tagged type coming from another HList (another LabelledGeneric to be exact), but the compiler yields me an error saying that it cannot find the implicit.

The exact error is (enabling -x-log-implicits):

shapeless.this.DefaultSymbolicLabelling.mkDefaultSymbolicLabelling is not a valid implicit value for shapeless.DefaultSymbolicLabelling.Aux[Inner with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("c")],Inner],K] because:
hasMatchingSymbol reported error: Inner with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("c")],Inner] is not case class like or the root of a sealed family of types

This is the snippet:

case class Inner(a: Int)
case class Outer(b: Int, c: Inner)

val gen = LabelledGeneric[Outer]

val inner = Inner(1)
val outer = Outer(2, inner)

def getGen[A, L](x: A)(implicit gen: LabelledGeneric.Aux[A, L]) = gen

val hOuter = gen.to(outer)

getGen(hOuter.at(1))

Am I missing something obvious here?

Thanks.

EDIT:

I added the definitions of the case classes. I'm also using scala 2.11.8 and shapeless 2.3.0


Solution

  • LabelledGeneric[Outer] takes values of type Outer onto shapeless records. These are HLists of fields, each of which is a value tagged with its "label" ... a singleton type corresponding to the name of the field in the case class. A labelled value of type T is a subtype of type T, but the two types are not the same. Usually this is absolutely fine.

    In your scenario you are taking the field hOuter.at(1) which is of type FieldType['c.type, Inner]. This is a type alias which expands to the following mouthful,

    Inner with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("c")],Inner]
    

    You then ask for the LabelledGeneric instance corresponding to that type. Unfortunately, whilst there is a LabelledGeneric instance for Inner, there's no instance for FieldType['c.type, Inner], because, as the compiler error message says (wrt the expanded type),

    Inner with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("c")],Inner]
    is not case class like or the root of a sealed family of types
    

    If you're not actually using the labels for anything, then the simplest fix for this is to use Generic instead of LabelledGeneric throughout, or at least in the definition of gen.

    If you are using the labels then the solution to your problem here is to strip them off the fields. The simplest way of doing that, which will work here but doesn't generalize, is to rely on Inner being a subtype of FieldType['c.type, Inner] and use a type ascription,

    getGen(hOuter.at(1): Inner)
    

    This means that the type parameter A of getGen will be inferred as Inner and the implicit lookup for LabelledGeneric[A] will succeed.

    A more general solution would make use of shapeless's Values type class which takes a shapeless record and strips the labels off each of its elements yielding an HList consisting of just the values.