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

68 views Asked by At

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?

1

There are 1 answers

1
MartinHH On BEST ANSWER

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