Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove repetitive case statements for implementations of a sealed trait

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.

like image 908
fommil Avatar asked Sep 29 '22 07:09

fommil


1 Answers

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

like image 115
fommil Avatar answered Oct 06 '22 19:10

fommil