How to append or prepend an element to a tuple in Scala

前端 未结 4 1635
灰色年华
灰色年华 2020-12-05 07:03

I have a tuple and want to add an element without loosing type safety. This is what I want to achieve:

val tuple = (\"\", 1, 1f) // (String, Int, Float)

val         


        
相关标签:
4条回答
  • 2020-12-05 07:29

    You can try my experimental macros library based on macro paradise scala branch at https://github.com/leonardschneider/macrogen which does exactly your example.

    Right now I still have some bugs though, where I can not chain the calls.

    0 讨论(0)
  • 2020-12-05 07:35

    I found a solution with the help of the awesome Shapeless library and it's HList

    /*
      Define an implicit class to add the methods
      It accepts any tuple as we have typed it as Product
    */
    implicit class TupleOps[A <: Product](t: A) {
    
      /*
        Declare the method to append
    
        B - The type of element we want to append
        L - The HList representing the tuple A
        P - The HList after appending B
        R - The final result
      */
      def :+[B, L <: HList, P <: HList, R <: Product](b: B)(
        /*
          We need some tools to help with the conversion
    
          hlister - Converts a tuple into an HList
          prepend - Can prepend one HList before another
          tupler  - Can convert an HList into a tuple
        */
        implicit hlister: HListerAux[A, L], prepend: PrependAux[L, B :: HNil, P], tupler: TuplerAux[P, R]):R =
          // Let the helpers do their job
          tupler(prepend(hlister(t), b :: HNil))
    
      /*
        The prepend method is similar to the append method but does not require
        the extra effort to append
      */
      def +:[B, L <: HList, R <: Product](b: B)(
        // Here we use the :: type of shapeless
        implicit hlister: HListerAux[A, L], tupler: TuplerAux[B :: L, R]):R =
          tupler(b :: hlister(t))
    }
    
    // usage is like this
    ("", 1, 1f) :+ 1d  //> res0: (String, Int, Float, Double) = ("",1,1.0,1.0)
    1d +: ("", 1, 1f)  //> res1: (Double, String, Int, Float) = (1.0,"",1,1.0)
    

    Edit

    In some cases, where you need to deal with implicit conversions this solution will not work in combination with case classes. I now reverted to the following implementation (based on the code of Rex Kerr)

    def char(n: Int) = ('A' + n).toChar
    def prop(n: Int) = "t._" + (n + 1)
    
    val result =
      for (n <- 1 to 21) yield {
    
        val range = (0 until n)
        val tupleTypeParams = range map char mkString ", "
        val tupleProperties = range map prop mkString ", "
    
        val elementType = char(n)
        val elementProperty = prop(n)
    
        val first = n == 1
        val tupleType = if (first) s"Tuple1[$tupleTypeParams]" else s"($tupleTypeParams)"
        val tupleInstance = if (first) s"Tuple1($tupleProperties)" else s"($tupleProperties)"
    
        val resultType = s"($tupleTypeParams, $elementType)"
    
        s"""|implicit def tupleOps$n[$tupleTypeParams, $elementType] = 
            |  new TupleAppendOps[$tupleType, $elementType, $resultType] {
            |    def append(t: $tupleType, e: $elementType) = ($tupleProperties, e)
            |    def init(t: $resultType) = $tupleInstance
            |    def last(t: $resultType) = $elementProperty
            |  }""".stripMargin
      }
    
    println(result mkString "\n")
    
    0 讨论(0)
  • 2020-12-05 07:49

    It's worth noting that you can also write a code generator for this in a few lines:

    val tupadd = for (n <- 2 to 20) yield {
      val t = (0 until n).map(i => ('A'+i).toChar).mkString(", ")
      val u = ('A'+n).toChar
      val i = (0 until n).map(i => "x._"+(i+1)).mkString(", ")
      List(
        s"implicit class TupOps$n[$t](val x: ($t)) extends AnyVal {",
        s"  def :+[$u](y: $u) = ($i, y)",
        s"  def +:[$u](y: $u) = (y, $i)",
        "}"
      ).mkString("\n")
    }
    

    Print these out, stick 'em in a file, and you're good to go:

    scala> implicit class TupOps2[A, B](val x: (A, B)) extends AnyVal {
         |   def :+[C](y: C) = (x._1, x._2, y)
         |   def +:[C](y: C) = (y, x._1, x._2)
         | }
    defined class TupOps2
    
    scala> (1,"salmon") :+ true
    res15: (Int, String, Boolean) = (1,salmon,true)
    
    0 讨论(0)
  • 2020-12-05 07:49

    Shapeless now does it. Adding

    import shapeless.syntax.std.tuple._
    

    before your code just compiles.

    0 讨论(0)
提交回复
热议问题