5

I often find that I need to extract the type of a sealed trait before doing the same thing to each implementation:

sealed trait Trait
case class Foo() extends Trait
case class Bar() extends Trait
// ... lots of other implementations

// *must* take a `Trait`, not a `T <: Trait`
def thing(t: Trait): ??? = t match {
  case f: Foo => // something with the instance and specific type
  case b: Bar => // something with the instance and specific type 
  // ... same thing again for other implementations
}

for example

// typically provided by somebody else...
trait Thing[T] { def thingy: String }
implicit def thing[T]: Thing[T] = new Thing[T] { def thingy = "stuff" }

def thing(t: Trait): String = t match {
  case Foo() => implicitly[Thing[Foo]].thingy
  case Bar() => implicitly[Thing[Bar]].thingy
  // ...
}

I'd like to reduce the boilerplate involved in doing this.

fommil
  • 5,757
  • 8
  • 41
  • 81
  • 2
    The question doesn't really make it clear why you require shapeless as a solution. `def thing[A <: Trait: Thing](a: A)` seems sufficient the way the question is posed. – Michael Zajac Apr 15 '15 at 13:28
  • The API must be `def thing(t: Thing)` not `def thing[T <: Thing](t: T)` – fommil Apr 15 '15 at 13:32
  • Do you find that instances for individual constructors works okay? It still seems super wrong to me but I don't know if that's just anti-subtyping bigotry. – Travis Brown Apr 15 '15 at 13:40
  • @TravisBrown you mean instances of `Thing` for each `T`? That doesn't change the problem definition because I'm still left with a massive pattern match with the same code on each right-hand side. – fommil Apr 15 '15 at 13:48
  • Right, I was (off-topically) asking about your experiences with the approach in general—probably better to take up somewhere else. – Travis Brown Apr 15 '15 at 13:51
  • this comes up a lot for me when I'm writing custom marshallers or implementing the TypeClass pattern. – fommil Apr 15 '15 at 13:58

1 Answers1

4

UPDATE: nowadays we'd use typeclass derivation via shapeless. e.g. https://github.com/fommil/shapeless-for-mortals

It turns out that you can use shapeless' polymorphic functions and co-product to do this:

  object thing extends Poly1 {
    implicit def action[T <: Trait: Thing] = at[T](
      a => implicitly[Thing[T]].thingy
    )
    // magic that makes it work at the sealed trait level
    def apply(t: Trait): String =
      Generic[Trait].to(t).map(thing).unify
  }

which can then be used like

println(thing(Foo(): Trait))

I'd like to make this easier to write via an abstract class (let's forget about passing on implicit parameters to action for now), e.g.

abstract class MatchSealed[In, Out] extends Poly1 {
  implicit def poly[T <: In] = at[T](action)

  def action[T <: In](t: T): Out

  import ops.coproduct.Mapper
  def apply[R <: HList](in: In)(
    implicit
      g: Generic.Aux[In, R],
      m: Mapper[this.type, R]
  ): Out = {
    val self: this.type = this
    g.to(in).map(self).unify
  }
}

but this is failing with a missing Mapper[self.type, g.Repr] on the final line. I don't know which implicit is missing, but I suspect it is the self.type. I really want to capture realisedSelf.type but I don't know how to do that.

UPDATE: it turns out that it is not possible to obtain the Mapper because it needs access to the realised object Unable to map on HList

Community
  • 1
  • 1
fommil
  • 5,757
  • 8
  • 41
  • 81
  • @milessabin can you think of any way to abstract the `apply` logic into an abstract class? I tried, but failed because `.map` isn't defined for any old `Generic` and I need some evidence that I don't know how to encode. – fommil Apr 15 '15 at 13:25
  • 1
    `apply[T](t: T)` would need an implicit `Generic` and an implicit `Mapper` of `thing` over the representation type. – Miles Sabin Apr 15 '15 at 14:03
  • I was hoping to make those things implicit parameters for the abstract class to avoid recomputation, but that could work. – fommil Apr 15 '15 at 14:38
  • @MilesSabin `Mapper` takes two type parameters. The first is the Poly (so I assume it would be `Generic[Thing]`) but what is the second one `extends Coproduct`? – fommil Apr 16 '15 at 13:53
  • Did you ever get the MatchedSealed abstract class working? – Upio Mar 16 '16 at 23:57
  • Thanks for posting this! I was wondering for quite some time how to achieve this with Shapeless. Note that I had to wrap the your example's `object thing extends Poly1 { ... }` using another wrapper object to work around a `java.lang.NoClassDefFoundError` when running a demo application (with shapeless 2.3.0 and Scala 2.11.8). Looks like I can finally throw out my customized macros and very elegantly "lift" my ADTs back into the type-level using your method. – Landlocked Surfer Apr 17 '16 at 19:44
  • 1
    @LandlockedSurfer this approach is old hat, use the approach in the linked talk. – fommil Apr 17 '16 at 19:54