3

I am trying to find a way, in shapeless, to prove that a given product type extends no sealed trait, and hence does not belong to any coproduct. Given the following sealed trait hierarchy:

  sealed trait Foo
  case class Bar(a: Char) extends Foo
  case class Baz(b: Int)  extends Foo    

I know I can use shapeless.ops.coproduct.Basis to prove that a given choice, or subsequence of choices, belongs to a co-product. Eg:

import shapeless._

type L = Bar :+: Baz :+: CNil

implicitly[ops.coproduct.Basis[L, Bar :+: CNil]]

What I am after now, is an operation to get the coproduct from a choice. Eg. given Bar or Baz, I would like to get back L, or alternatively, the type of the sealed base trait Foo.

Is this something that shapeless can do? alternatively, is it possible to do it with macros?

Update:

I ended up writing a fairly involved implicit macro...

import scala.language.experimental.macros
import scala.reflect.macros.whitebox

trait SealedBaseTraitOf[A] {
  type Repr
}

object SealedBaseTraitOf {
  def materializeImpl[A](c: whitebox.Context)(implicit tag: c.WeakTypeTag[A]): c.Expr[SealedBaseTraitOf[A]] = {
    import c.universe._
    val a           = weakTypeOf[A].typeSymbol
    val baseClasses = a.asClass.baseClasses
    val maybeBaseTrait =
      baseClasses
        .find(t => t.asClass.isTrait && t.asClass.isSealed)
        .map(_.asType.name)

    val repr = maybeBaseTrait.map(t => tq"$t").getOrElse(tq"Nothing")
    println(s"Got a repr: $repr")
    c.Expr[SealedBaseTraitOf[A]](q"""
        new SealedBaseTraitOf[$a] {
          type Repr = $repr
        }
      """)
  }

  implicit def materialize[A]:SealedBaseTraitOf[A] = macro materializeImpl[A]
}

Putting all together, the macro can be used as follows:

  case class ExtendsNothing(a: Int)

  sealed trait X
  case class ExtendsX(b: Char) extends X

  import shapeless._

  val bt1 = the[SealedBaseTraitOf[ExtendsNothing]]
  implicitly[bt1.Repr =:= Nothing]

  val bt2 = the[SealedBaseTraitOf[ExtendsX]]
  implicitly[bt2.Repr =:= X]

  val coprodX   = Generic[X]
  val coprodBt2 = Generic[bt2.Repr]

  implicitly[coprodX.Repr =:= coprodBt2.Repr]
  implicitly[ops.coproduct.Basis[coprodBt2.Repr, ExtendsX :+: CNil]]

While getting close to a solution, I am still hoping to find something a bit less involved, possibly which doesn't involve using macros.

Andrea Fiore
  • 1,628
  • 2
  • 14
  • 18
  • What if there are several such traits? ` sealed trait Foo0; sealed trait Foo extends Foo0; sealed trait Foo1; sealed trait Foo2; case class Bar(a: Char) extends Foo with Foo1; case class Baz(b: Int) extends Foo with Foo2` – Dmytro Mitin Nov 10 '17 at 17:07
  • I don't think such macro exists in shapeless. I guess this can be done with macros or reflection. https://stackoverflow.com/questions/28067387/is-there-a-way-to-get-the-direct-parents-of-a-classsymbol-in-macro-context http://grokbase.com/t/gg/scala-user/134qe1zx67/macros-inspecting-parent-types-of-the-enclosing-type – Dmytro Mitin Nov 10 '17 at 17:14
  • @DmytroMitin I guess in that circumstance it would be enough to identify "lowest upper bound" (i.e. `Foo`). However, that implies we can statically figure out the order in which traits are extended. – Andrea Fiore Nov 10 '17 at 17:34

0 Answers0