Johny T Koshy
Johny T Koshy

Reputation: 3922

When trying to convert a Map to scala objects using Shapeless, Coproduct derivatives are not picked up the compiler

I am trying to convert Map[String, AttributeValue] to Scala objects. AttributeValue represents data in DynamoDB. FromAttributeValue in the below stub is used to convert basic cases

Following is code stub:

trait FromMap[L] {
  def apply(m: Map[String, AttributeValue]): Option[L]
}

object FromMap extends LowerPriorityFromMapInstances {

  implicit val hnilFromMap: FromMap[HNil] = ???

  implicit def hconsFromMap0[K <: Symbol, V, T <: HList](implicit
      witness: => Witness.Aux[K],
      fromAttributeValue: => Lazy[FromAttributeValue[V]],
      fromMapT: => FromMap[T],
  ): FromMap[FieldType[K, V] :: T] = ???

  implicit def cnilFromMap: FromMap[CNil] = ???

  implicit def coproductFromMap[K <: Symbol, H, T <: Coproduct](implicit 
      wit: => Witness.Aux[K],
      fromMapH: => Lazy[FromMap[H]],
      fromMapT: => FromMap[T]
  ): FromMap[FieldType[K, H] :+: T] = ???
}

trait LowerPriorityFromMapInstances {

//Other instances as required


  implicit def hConsFromMap1[K <: Symbol, V, R <: HList, T <: HList](implicit
      wit: => Witness.Aux[K],
      gen: => LabelledGeneric.Aux[V, R],
      fromMapH: => Lazy[FromMap[R]],
      fromMapT: => FromMap[T]
  ): FromMap[FieldType[K, V] :: T] = ???
 
}

Following is a scastie for the same.

Compiler is able to find derivations for all cases except for sealed traits. How can I make this work?

Upvotes: 1

Views: 99

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51703

Firstly, since your representation types are now not only HLists, but also Coproducts, you should remove upper bounds <: HList in these two places

implicit class FromMapOps[L /*<: HList*/](val a: Map[String, AttributeValue]) extends AnyVal { ...
//                            ^^^^^^^^
implicit def hConsFromMap1[K <: Symbol, V, R /*<: HList*/, T <: HList](implicit ...
//                                             ^^^^^^^^

Secondly, in implicit class FromMapOps you're mixing recursion logic with definition of extension method, so you don't actually define an instance of type class FromMap for a case class (and sealed trait). But if you start to resolve Map.empty[String, AttributeValue].toObj[ContainerUser] then you'll see that you need such instance, namely FromMap for GroupContainer

Map.empty[String, AttributeValue].toObj[ContainerUser]

implicitly[FromMap[FieldType[Witness.`'GroupContainer`.T, GroupContainer] :+: CNil]](
    FromMap.coproductFromMap(
      implicitly[Witness.Aux[Witness.`'GroupContainer`.T]],
      Lazy(implicitly[FromMap[GroupContainer]]),
      implicitly[FromMap[CNil]]
    )
  )

implicitly[FromMap[GroupContainer]]

So better don't mix recursion logic with logic defining extension method, keep logic defining instances of a type class and logic defining extension method separate. Try to define an instance of type class

implicit def genericFromMap[A, L](implicit
                                      gen: => LabelledGeneric.Aux[A, L],
                                      fromMap: => FromMap[L],
                                 ): FromMap[A] = ???

and an extension method

implicit class FromMapOps[L](val a: Map[String, AttributeValue]) extends AnyVal {
  def toObj[A](implicit
                fromMap: FromMap[A]
              ): Option[A] = fromMap(a)
}

https://scastie.scala-lang.org/DmytroMitin/JwhJuXiXRBOT08RjZyywKg/2 (NotImplementedError means that the code compiles.)

You can find several SO questions about converting between a case class and map (search for case class to map scala, map to case class scala).

Upvotes: 2

Related Questions