Search code examples
scalashapelesslabelled-generic

Shapeless align with different types but same labels


Does someone know how to align two records when they have the same name of fields but not the same values one has values wrapped in Column another one wrapped in Option and I need to align them as the order is different

import shapeless._
import shapeless.labelled.{FieldType, field}
import shapeless.ops.hlist.{Align, ToTraversable, ZipWith}

object ShapelessTest {


  def zipClasses[A, B, P <: Poly2, ARepr <: HList, BRepr <: HList, R <: HList, X]
  (a: A, b: B, f: P)(
    implicit
    aGen : LabelledGeneric.Aux[A, ARepr],
    bGen : LabelledGeneric.Aux[B, BRepr],
//    align : Align[ARepr, BRepr],
    zipWith : ZipWith.Aux[ARepr, BRepr, P, R],
    toTrav: ToTraversable.Aux[R, List, X]
  ): List[X] =
    aGen.to(a).zipWith(bGen.to(b))(f).toList
//    align.apply(aGen.to(a)).zipWith(bGen.to(b))(f).toList



  def main(args: Array[String]): Unit = {


    case class Column[A](value: A)

    case class DTable(id: Column[Long], s2: Column[Int], s: Column[String])
    case class DFilter(id: Option[Long], s: Option[String], s2: Option[Int])

    object filter extends Poly2 {
      implicit def repr[K <: Symbol, V] = at[FieldType[K, Column[V]], FieldType[K, Option[V]]] { (a, b) =>
        field[K]( (a, b).asInstanceOf[(Any, Any)] )
      }
    }

    val dTable = new DTable(Column(1L), Column(3), Column("s"))
    val dFilter = new DFilter(Option(222L), Option("second"), Option(234))


//    def filter[A](col: Column[A], filterCriteria: Option[A]): Option[Boolean] = ???

//    def filterClass(table: DTable, filter: DFilter): List[Option[Boolean]] = ???
    def filterClass(t: DTable, f: DFilter): List[(Any, Any)] = zipClasses(t, f, filter)

    val res = filterClass(dTable, dFilter)
    println(res)

  }

}

I can align two records with exactly the same types and I can do zipWith with two records even if the type is not exact one but they need to be aligned by keys.

Thank you


Solution

  • If we ignore similarities in field names and focus on types we can do the following:

        def alignRecordByTypeInsideConstructor[A, B, L1 <: HList, L2 <: HList,
          F[_], G[_], L3 <: HList, L4 <: HList, Out <: HList](a: A, b: B, f: F ~> Id, g: Id ~> G)(
          implicit
          gen1: Generic.Aux[A, L1],
          gen2: Generic.Aux[B, L2],
          comapped1: Comapped.Aux[L1, F, L3],
          natTRel1: NatTRel[L1, F, L3, Id],
          comapped2: Comapped.Aux[L2, G, L4],
          natTRel2: NatTRel[L2, G, L4, Id],
          align: Align[L3, L4],
          mapped: Mapped.Aux[L4, G, Out],
          natTRel3: NatTRel[L4, Id, Out, G]
        ): Out =
          alignByTypeInsideConstructor[L1, L2, F, G, L3, L4, Out](gen1.to(a), gen2.to(b), f, g)
    
        def alignByTypeInsideConstructor[L1 <: HList, L2 <: HList,
          F[_], G[_], L3 <: HList, L4 <: HList, Out <: HList](l1: L1, l2: L2, f: F ~> Id, g: Id ~> G)(
          implicit
          comapped1: Comapped.Aux[L1, F, L3],
          natTRel1: NatTRel[L1, F, L3, Id],
          comapped2: Comapped.Aux[L2, G, L4],
          natTRel2: NatTRel[L2, G, L4, Id],
          align: Align[L3, L4],
          mapped: Mapped.Aux[L4, G, Out],
          natTRel3: NatTRel[L4, Id, Out, G]): Out =
          natTRel3.map(g, align(natTRel1.map(f, l1)))
    
        object columnToId extends (Column ~> Id) {
          override def apply[A](column: Column[A]): A = column match {
            case Column(a) => a
          }
        }
    
        object idToOption extends (Id ~> Option) {
          override def apply[A](a: A): Option[A] = Some(a)
        }
    
        alignByTypeInsideConstructor(
          Column(1L) :: Column(2) :: Column("a") :: HNil,
          Option(3L) :: Option("b") :: Option(4) :: HNil,
          columnToId,
          idToOption
        )
    
      //Some(1) :: Some(a) :: Some(2) :: HNil
    
        alignRecordByTypeInsideConstructor(
          DTable(Column(1L), Column(2), Column("a")),
          DFilter(Option(3L), Option("b"), Option(4)),
          columnToId,
          idToOption
        )
    
      //Some(1) :: Some(a) :: Some(2) :: HNil