Search code examples
scalapolymorphismtuplesshapeless

Polymorphic function to convert tuples of arbitrary size


I would like to create polymorphic function taking a list of various sized tuples as parameter and return a list with the same type. Therefore, the code inside the function should be adapted too. Here an example to explain my problem and my aim :

val l2 = List( ("1","2"), ("3","4"))
val l3 = List( ("1","2","3"), ("4","5","6"))
val k2 = List( ("1","3"), ("5","7"))
val k3 = List( ("1","3","5"), ("7","9","11"))

def convertTuple2 ( l : List[(String,String)] ) : List[(Int,Int)] = {
    if (l.contains("1","2")) l.map{ case (a,b) => (a.toInt+10, b.toInt+10) }
    else l.map{ case (a,b) => (a.toInt, b.toInt) }
}

def convertTuple3 ( l : List[(String,String,String)] ) : List[(Int,Int,Int)] = {
    if (l.contains("1","2","3")) l.map{ case (a,b,c) => (a.toInt+10, b.toInt+10, c.toInt+10)}
    else l.map{ case (a,b,c) => (a.toInt, b.toInt, c.toInt) }
}

val l2converted = convertTuple2(l2)
val l3converted = convertTuple3(l3)
val k2converted = convertTuple2(k2)
val k3converted = convertTuple3(k3)

println(l2converted.mkString(",")) //printing (11,12),(13,14)
println(l3converted.mkString(",")) //printing (11,12,13),(14,15,16)
println(k2converted.mkString(",")) //printing (1,3),(5,7)
println(k3converted.mkString(",")) //printing (1,3,5),(7,9,11)

In other words, how to create a unique function being both like convertTuple2(...) and convertTuple3(...) and possibly also convertTupleN(...) please?

It means that l.contains(...) and the pattern matching { case ... => ... } elements have to also be adapted according the size of the tuples from the input list.

What would be a simple, elegant and efficient solution?


Solution

  • Try

    import shapeless.{HList, Nat, Poly1}
    import shapeless.ops.hlist.Tupler
    import shapeless.ops.tuple.{Length, Mapper}
    import shapeless.ops.nat.ToInt
    import shapeless.syntax.nat._
    import shapeless.syntax.std.tuple._
    import shapeless.poly.->
    import shapeless.nat._
    
    object toStringPoly extends Poly1 {
      implicit def `case`[N <: Nat](implicit 
        toInt: ToInt[N]
      ): Case.Aux[N, String] = at(_ => toInt().toString)
    }
    
    object toIntPoly extends (String -> Int)(_.toInt)
    object toIntPoly1 extends (String -> Int)(s => s.toInt + 10)
    
    def convertTuple[A <: Product, 
                     N <: Nat, 
                     L <: HList, 
                     B <: Product, 
                     C <: Product](l: List[A])(implicit
      length: Length.Aux[A, N],
      range: (_1 *--* N) { type Out = L },
      tupler: Tupler.Aux[L, B],
      toStringMapper: Mapper[B, toStringPoly.type],
      toIntMapper: Mapper.Aux[A, toIntPoly.type, C],
      toIntMapper1: Mapper.Aux[A, toIntPoly1.type, C],
    ) : List[C] =
      if (l.contains(range().tupled.map(toStringPoly))) 
        l.map(_.map(toIntPoly1))
      else l.map(_.map(toIntPoly))
    

    or

    import scala.collection.immutable.IndexedSeq
    import shapeless.{HList, Nat, unexpected}
    import shapeless.ops.hlist.Tupler
    import shapeless.ops.tuple.{Length, Mapper}
    import shapeless.ops.nat.ToInt
    import shapeless.ops.traversable.ToSizedHList
    import shapeless.syntax.std.tuple._
    import shapeless.poly.->
    
    object toIntPoly extends (String -> Int)(_.toInt)
    object toIntPoly1 extends (String -> Int)(s => s.toInt + 10)
    
    def convertTuple[A <: Product, N <: Nat, L <: HList, C <: Product](l: List[A])(implicit
      length: Length.Aux[A, N],
      toInt: ToInt[N],
      toSizedHList: ToSizedHList.Aux[IndexedSeq, String, N, Option[L]],
      tupler: Tupler[L],
      toIntMapper: Mapper.Aux[A, toIntPoly.type, C],
      toIntMapper1: Mapper.Aux[A, toIntPoly1.type, C],
    ) : List[C] =
      if (l.contains(toSizedHList((1 to toInt()).map(_.toString)).getOrElse(unexpected).tupled))
        l.map(_.map(toIntPoly1))
      else l.map(_.map(toIntPoly))