Search code examples
scalashapeless

LabelledGenerid.Aux implicit not available when using generic type in function body


I'm trying this and it works:

`
    case class Foo(name: String)

class Morphic(map: Map[String, Any]) { def add(k: String, v: Any) = { new Morphic((map + (k -> v))) } def to[T](): T = { def toClass[A]: ToCase[A] = new ToCase[A] // This is class to convert from Map to case class val res = toClass[Foo].from(map).get // <-- problem is here - cannot use T res.asInstanceOf[T] } } object testApp extends App { var m = new Morphic(Map[String, Any]()) var m1 = m.add("name", "john") println(m1.to[Foo]) }

I should use T instead of Foo in val res = toClass[T].from(map).get but it doesn't compile saying implicit is missing

toClass[T].from is a function creating a given type of case class from Map

How do I make that implicit (and possibly others on which .from relies) available?

I tried def to[T, H <: HList]()(implicit gen: LabelledGeneric.Aux[A, H]) = ... but then I need to specify both types when calling the .to and I can't figure out what to specify for H

Thanks


Solution

  • You can transform a Map into HList, and then the HList into T:

    import shapeless.{::, HList, HNil, LabelledGeneric, Witness}
    import shapeless.labelled._
    
    case class Foo(name: String)
    
    trait MapToHList[L <: HList] {
      def apply(map: Map[String, Any]): Option[L]
    }
    object MapToHList {
      implicit object hNilMapToHList extends MapToHList[HNil] {
        override def apply(map: Map[String, Any]): Option[HNil] = Some(HNil)
      }
    
      implicit def hConsMapToHList[K <: Symbol, V, T <: HList](implicit
                                                               mapToHList: MapToHList[T],
                                                               witness: Witness.Aux[K]
                                                              ): MapToHList[FieldType[K, V] :: T] =
        new MapToHList[FieldType[K, V] :: T] {
          override def apply(map: Map[String, Any]): Option[FieldType[K, V] :: T] = {
            val str = witness.value.toString.tail
            for {
              v <- map.get(str)
              t <- mapToHList(map)
            } yield field[K](v.asInstanceOf[V]) :: t
          }
        }
    }
    
    trait ToCase[A] {
      def from(map: Map[String, Any]): Option[A]
    }
    object ToCase {
      implicit def mkToCase[A, L <: HList](implicit
                                           gen: LabelledGeneric.Aux[A, L],
                                           mapToHList: MapToHList[L]
                                          ): ToCase[A] =
        new ToCase[A] {
          override def from(map: Map[String, Any]): Option[A] = mapToHList(map).map(gen.from)
        }
    }
    
    
    class Morphic(map: Map[String, Any]) {
    
      def add(k: String, v: Any) = {
        new Morphic((map + (k -> v)))
      }
    
      def to[T](implicit toCase: ToCase[T]): T = toCase.from(map).get
    
    }
    
    object testApp extends App {
      var m = new Morphic(Map[String, Any]())
      var m1 = m.add("name", "john")
      println(m1.to[Foo]) // Foo(john)
    }
    

    I tried def to[T, H <: HList]()(implicit gen: LabelledGeneric.Aux[A, H]) ... but then I need to specify both types when calling the .to and I can't figure out what to specify for H

    You can call it as m1.to[Foo, FieldType[Witness.`'name`.T, String] :: HNil]() or m1.to[Foo, Record.`'name -> String`.T]().