Search code examples
scalashapeless

Collect instances via LiftAll


I'm trying to describe the types which a case class contains.

import shapeless._
import shapeless.ops.hlist.LiftAll

trait Desc[T] {
  def description: String
}

case class Foo(f: Int)
object Foo {
  implicit val description: Desc[Foo] = new Desc[Foo] { val description = "foo" }
}

case class SomeCaseClass(f: Foo)

val gen = Generic[SomeCaseClass]
val lifted = implicitly[LiftAll[Desc, gen.Repr]].instances.toList

Gives me

could not find implicit value for parameter toTraversableAux: shapeless.ops.hlist.ToTraversable.Aux[shapeless.ops.hlist.LiftAll[Playground.this.Desc,Playground.this.gen.Repr]#Out,List,Lub]
not enough arguments for method toList: (implicit toTraversableAux: shapeless.ops.hlist.ToTraversable.Aux[shapeless.ops.hlist.LiftAll[Playground.this.Desc,Playground.this.gen.Repr]#Out,List,Lub])toTraversableAux.Out.
Unspecified value parameter toTraversableAux.

Scastie here: https://scastie.scala-lang.org/bXu71pMQQzCqrrsahVBkWA


Solution

  • When you summon an implicit instance with implicitly[LiftAll[Desc, gen.Repr]] then the dependent type Out of LiftAll is lost, so the compiler doesn't know which type exactly instances will return.

    To work around this problem most typeclasses in Shapeless define an apply method in their companion object which does retain all dependent type information. It's the reason that you can use gen.Repr in a meaningful way after calling val gen = Generic[SomeCaseClass]. For some reason however LiftAll.apply was not implemented in this way. So that leaves you the option of implementing your own implicitly, or since you're using Shapeless anyway, use its the which is supposed to be a better implicitly.

    scala> def impl[T <: AnyRef](implicit ev: T): ev.type = ev
    impl: [T <: AnyRef](implicit ev: T)ev.type
    
    scala> impl[LiftAll[Desc, gen.Repr]].instances.toList
    res1: List[Desc[Foo]] = List(Foo$$anon$1@40b3708a)
    
    scala> the[LiftAll[Desc, gen.Repr]].instances.toList
    res2: List[Desc[Foo]] = List(Foo$$anon$1@40b3708a)
    

    You can see the difference here in the inferred types that the REPL displays:

    scala> impl[LiftAll[Desc, gen.Repr]]
    res3: LiftAll.Aux[Desc,Foo :: HNil,Desc[Foo] :: HNil] = shapeless.ops.hlist$LiftAll$$anon$206@384d060c
    
    scala> implicitly[LiftAll[Desc, gen.Repr]]
    res4: LiftAll[Desc,gen.Repr] = shapeless.ops.hlist$LiftAll$$anon$206@30787774