scalamappingshapelesstype-level-computationhlist

Type level mapping over a HList


I am handrolling my database API and would essentially like to model column families as a HList of columns, with the latter loosely being a Seq[_], so somewhere I have a type like Column[String]::Column[Int]::Column[Double]::HNil, with all elements sharing a common type constructor.

What would be the simplest way of expressing the type of rows, ie String::Int::Double::HNil, from the type given above, essentially unwrapping the inner types? My current reasoning is that since shapeless can do a map over that HList given the right poly, one should be able to (ab)use the depedent type Out of the Mapper trait.

One thing I can think of is just implementing a useless poly with the right cases, like a Case.Aux[Column[T],T] for all Ts then summon a Mapper for it et voilá, there I have my Out, but this feels a bit hacky and I'm not sure it would even work. On the other hand, I do not yet feel that comfortable around dependent types and type recursion to really want to try and implement something which shapeless obviously already does.

Thank you for any input!


Solution

  • Try

    import shapeless.PolyDefns.~>
    import shapeless.ops.hlist.{Comapped, NatTRel}
    import shapeless.{HList, HNil, Id}
    
    object App {
      case class Column[A](a: A)
    
      def extract[L <: HList, L1 <: HList](l: L)(implicit
        comapped: Comapped.Aux[L, Column, L1],
        natTRel: NatTRel[L, Column, L1, Id],
      ): L1 = natTRel.map(new (Column ~> Id) { def apply[T](col: Column[T]) = col.a }, l)
    
      val result = extract(Column(1) :: Column("a") :: HNil)
    
      def main(args: Array[String]): Unit = {
        println(result) // 1 :: a :: HNil
      }
    }
    

    or

    import shapeless.PolyDefns.~>
    import shapeless.ops.hlist.NatTRel
    import shapeless.{HList, HNil}
    
    object App {
      case class Column[A](a: Seq[A])
    
      def extract[L <: HList, L1 <: HList](l: L)(implicit
        natTRel: NatTRel[L, Column, L1, Seq],
      ): L1 = natTRel.map(new (Column ~> Seq) { def apply[T](col: Column[T]): Seq[T] = col.a }, l)
    
      val result = extract(Column(Seq("a", "b")) :: Column(Seq(1, 2)) :: Column(Seq(10.0, 20.0)) :: HNil)
    
      def main(args: Array[String]): Unit = {
        println(result) // List(a, b) :: List(1, 2) :: List(10.0, 20.0) :: HNil
      }
    }