scalafunctional-programmingshapelesshlist

Is there a way to convert Hlist to an appropriate case class in a generic way?


I have looked at the cool solution presented by Travis Brown which allows converting case classes between each other in a generic way. I tried to use it to convert HList to a case class but did not managed to get it worked. Here is my attempt:

import shapeless._, ops.hlist.Align
import syntax.std.tuple._

object Shplss  extends App {
  class SameFieldsConverter[T] {
    def apply[S, SR <: HList, TR <: HList](s: S)(implicit
                                                 genS: LabelledGeneric.Aux[S, SR],
                                                 genT: LabelledGeneric.Aux[T, TR],
                                                 align: Align[SR, TR]
    ) = genT.from(align(genS.to(s)))
  }

  def convertTo[T] = new SameFieldsConverter[T]

  type SomeType = Int :: Int :: String :: Boolean :: Int :: Int :: HNil
  final case class SomeProductType(f1: Int, f2: Int, f3: String, f4: Boolean, f5: Int, f6: Int)

  val some: SomeType = (4, 4, "ssdf", true, 2, 4).productElements

  convertTo[SomeProductType](some)
}

Unfortunately it fails with the error:

Error:(22, 29) could not find implicit value for parameter genS: shapeless.LabelledGeneric.Aux[com.test.Shplss.SomeType,SR]
  convertTo[SomeProductType](some)


Error:(22, 29) not enough arguments for method apply: (implicit genS: shapeless.LabelledGeneric.Aux[com.test.Shplss.SomeType,SR], implicit genT: shapeless.LabelledGeneric.Aux[com.test.Shplss.SomeProductType,TR], implicit align: shapeless.ops.hlist.Align[SR,TR])com.test.Shplss.SomeProductType in class SameFieldsConverter.
Unspecified value parameters genS, genT, align.
  convertTo[SomeProductType](some)

Is there a way to enhance the converTo[B] function so it can convert between HLists as well?


Solution

  • Shapeless's Generic and LabelledGeneric are type classes that provide a generic representation for case classes and sealed trait hierarchies using hlists and coproducts. If you already have an hlist, you don't really need a Generic instance, and Shapeless doesn't provide one. In your case this means you can actually skip the genS and SR parts:

    import shapeless._, ops.hlist.Align
    import syntax.std.tuple._
    
    object Shplss  extends App {
      class SameFieldsConverter[T] {
        def apply[S <: HList, TR <: HList](s: S)(implicit
          genT: Generic.Aux[T, TR],
          align: Align[S, TR]
        ) = genT.from(align(s))
      }
    
      def convertTo[T] = new SameFieldsConverter[T]
    
      type SomeType = Int :: Int :: String :: Boolean :: Int :: Int :: HNil
      final case class SomeProductType(f1: Int, f2: Int, f3: String, f4: Boolean, f5: Int, f6: Int)
    
      val some: SomeType = (4, 4, "ssdf", true, 2, 4).productElements
    
      convertTo[SomeProductType](some)
    }
    

    This will give you SomeProductType(4,4,ssdf,true,2,4), as you'd expect.

    Note that I've changed genT from LabelledGeneric to Generic, since we no longer have labels to align on the input side. I guess you could add some extra machinery to "inject" the unlabeled input into a Shapeless record to match the LabelledGeneric type, but in this specific use case at least there's not really any point.