Collecting data from nested class classes using Generic

Is it possible to provide a generic function that will traverse an arbitrary case class hierarchy and gather information from the selected fields? In the following snippet, such fields are encoded as Thing [T].

The snippet works great for most scenarios. The only problem is that Thing wraps a class of type (eg List [String]), and such a field is more nested in the hierarchy; when it is at the top level it works great.

import shapeless.HList._
import shapeless._
import shapeless.ops.hlist.LeftFolder

case class Thing[T](t: T) {
  def info: String = ???
}

trait Collector[T] extends (T => Seq[String])

object Collector extends LowPriority {
  implicit def forThing[T]: Collector[Thing[T]] = new Collector[Thing[T]] {
    override def apply(thing: Thing[T]): Seq[String] = thing.info :: Nil
  }
}

trait LowPriority {
  object Fn extends Poly2 {
    implicit def caseField[T](implicit c: Collector[T]) =
      at[Seq[String], T]((acc, t) => acc ++ c(t))
  }

  implicit def forT[T, L <: HList](implicit g: Generic.Aux[T, L],
                                   f: LeftFolder.Aux[L, Seq[String], Fn.type, Seq[String]]): Collector[T] =
    new Collector[T] {
      override def apply(t: T): Seq[String] = g.to(t).foldLeft[Seq[String]](Nil)(Fn)
    }
}

object Test extends App {
  case class L1(a: L2)
  case class L2(b: Thing[List[String]])

  implicitly[Collector[L2]] // works fine
  implicitly[Collector[L1]] // won't compile
}

      

+3


source to share


1 answer


I am afraid this is not possible. It looks like HList built compile time from statically known things. So when you wrap your types, for some reason it seems like HList can't infer the correct implications.

Here is a simple example built from a shapeless anti-aliasing example .

object Test extends App {
  import shapeless._
  import ops.tuple.FlatMapper
  import syntax.std.tuple._

  trait LowPriorityFlatten extends Poly1 {
    implicit def default[T] = at[T](Tuple1(_))
  }
  object flatten extends LowPriorityFlatten {
    implicit def caseTuple[P <: Product](implicit fm: FlatMapper[P, flatten.type]) =
      at[P](_.flatMap(flatten))
  }

  case class AT[T](a: T, b: T)
  case class A2T[T](a: AT[T], b: AT[T])
  case class A2(a: AT[Int], b: AT[Int])

  println(flatten(A2T(AT(1, 2), AT(3, 4))))
  println(flatten(A2(AT(1, 2), AT(3, 4))))
}

      

You would think it should print the same for A2T and A2, but it doesn't. It actually prints out:

(1,2,3,4)
(AT(1,2),AT(3,4))

      



So, I don't think you can use Shapeless to do what you want.

However! You can still walk the class hierarchy of your case in search of Things (just not with the formless). Check it!

object Test extends App {
  case class Thing[T](t: T) {
    def info: String = toString
  }

  def collect[T](t: T): Iterator[String] = t match {
    case t: Thing[_] => Iterator(t.info)
    case p: Product => p.productIterator.flatMap(collect)
    case _ => Iterator()
  }

  case class L0(a: L1)
  case class L1(a: L2)
  case class L2(a: Thing[List[String]])
  case class MT(a: L2, b: L2, c: Thing[Int])

  println("Case #1")
  collect(L0(L1(L2(Thing(List("a", "b", "c")))))).foreach(println)

  println("Case #2")
  collect(MT(L2(Thing(List("a", "c"))), L2(Thing(List("b"))), Thing(25))).foreach(println)
}

      

This has an output:

Case #1
Thing(List(a, b, c))
Case #2
Thing(List(a, c))
Thing(List(b))
Thing(25)

      

+1


source







All Articles