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

前端 未结 4 1686
灰色年华
灰色年华 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: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")
    

提交回复
热议问题