scalashapelesscirce

Can shapeless derive a "shallow" generic for ADT coproduct?


I'm trying to come up with a parser framework for ADT hierarchy. I want it to automatically derive a parser defined at either "leaf" (case class) level or "node" (intermediate sealed trait) level. Usage would look like:

sealed trait Chrum
case object Go extends Chrum
sealed trait Bigh extends Chrum
case class Do(doo: Double) extends Bigh
case class Bo(bo: Int) extends Bigh

implicit val goParser:Parser[Go] = ???
implicit val bighParser:Parser[Bigh] = ???
import MyParsers._
implicitly[Parser[Chrum]].parse(myinput) // implicicts in MyParsers would derive parser for Chrum from the above 2

I followed the "simple" approach from circe-derivation examples. It failed to find the bighParser, but worked if I defined separate doParser and boParser Root cause seems to be this:

Generic[Chrum]
//returns: shapeless.Generic[Chrum]{type Repr = Go.type :+: Do :+: Bo :+: shapeless.CNil}
//         Bigh is erased, no way to derive it's parser :(

I think what I need here is autoderivation of sth like Go.type :+: Bigh :+: CNil for the above hierarchy. Is this possible with shapeless?


Solution

  • One can never be 100% sure that something is not possible with shapeless (so I'm looking forward to answers proving me wrong), but: I think what you intend to do is not possible.

    Shapeless represents sealed trait hierarchies as Coproduct in a way that it will ignore any abstract members of the ADT:

    scala> sealed trait Chrum
         | case object Go extends Chrum
         | sealed trait Bigh extends Chrum
         | case class Do(doo: Double) extends Bigh
         | case class Bo(bo: Int) extends Bigh
    trait Chrum
    object Go
    trait Bigh
    class Do
    class Bo
    
    scala> shapeless.Generic[Chrum]
    val res2: shapeless.Generic[Chrum]{type Repr = Bo :+: Do :+: Go.type :+: shapeless.CNil} = shapeless.Generic$$anon$1@38d308e7
    

    Trying to force shapeless to provide your desired representation fails because the underlying macro implementation does not support that:

    scala> shapeless.Generic.materialize[Chrum, Bigh :+: Go.type :+: shapeless.CNil]
                                        ^
           error: type mismatch;
            found   : shapeless.Generic[Chrum]{type Repr = Bo :+: Do :+: Go.type :+: shapeless.CNil}
            required: shapeless.Generic.Aux[Chrum,Bigh :+: Go.type :+: shapeless.CNil]
               (which expands to)  shapeless.Generic[Chrum]{type Repr = Bigh :+: Go.type :+: shapeless.CNil}
    

    N.B.: what you intend to do would be possible with the derivation mechanisms of Scala 3 because there, sealed traits with nested sealed traits are represented differently:

    scala> summon[scala.deriving.Mirror.Of[Chrum]]
    val res0:
      scala.deriving.Mirror.Sum{
        type MirroredMonoType = Chrum; type MirroredType = Chrum;
          type MirroredLabel = "Chrum"; type MirroredElemTypes = (Go.type, Bigh);
          type MirroredElemLabels = ("Go", "Bigh")
      } = anon$1@36db5318