Olscream
Olscream

Reputation: 137

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?

Upvotes: 0

Views: 251

Answers (1)

Dmytro Mitin
Dmytro Mitin

Reputation: 51723

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))

Upvotes: 3

Related Questions