listas - Cómo agregar o anteponer un elemento a una tupla en Scala
scala ejemplos (4)
Encontré una solución con la ayuda de la increíble biblioteca Shapeless y es 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)
Editar
En algunos casos, cuando necesite lidiar con conversiones implícitas, esta solución no funcionará en combinación con clases de casos. Ahora volví a la siguiente implementación (basada en el código de 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")
Tengo una tupla y quiero agregar un elemento sin perder el tipo de seguridad. Esto es lo que quiero lograr:
val tuple = ("", 1, 1f) // (String, Int, Float)
val newTuple:(String, Int, Float, Double) = tuple :+ 1d
Puede probar mi biblioteca de macros experimentales basada en la rama de Scala de macro paradise en https://github.com/leonardschneider/macrogen que hace exactamente su ejemplo.
En este momento todavía tengo algunos errores, sin embargo, no puedo encadenar las llamadas.
Sin forma ahora hace el truco. Añadiendo
import shapeless.syntax.std.tuple._
antes de que tu código simplemente compile.
Vale la pena señalar que también puede escribir un generador de código para esto en unas pocas líneas:
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")
}
Imprime estos, pégalos en un archivo y listo:
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)