2010-10-12 21 views
6

que quiero hacer algo como esto (simplificado bastante fuerte):Trabajar con tuplas de Scala

((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped map (_ + _) 

Ignorar los valores reales de los números enteros (aunque es importante que estos son 6-tuplas, en realidad :)) . Esencialmente, quiero usar esto con bastante regularidad en una función que mantiene un Map[String, (Int, Int, Int, Int, Int, Int)] cuando se actualiza un elemento existente.

Como es, Scala escupe esto a mí:

<console>:6: error: could not find implicit value for parameter w1: ((Int, Int, Int, Int, Int, Int)) => scala.collection.TraversableLike[El1,Repr1] 
    ((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped 

Si uso Seq s en lugar de tuplas, todo funciona bien, pero quiero hacer cumplir una aridad de 6 en el sistema de tipo (I Probablemente type Record = (Int, Int, Int, Int, Int, Int) como refactor rápido en breve).

¿Alguien puede ofrecer algún consejo sobre lo que estoy haciendo mal/por qué Scala no se ocupará del código anterior? Pensé que podría funcionar si usaba una tupla de 2 ó 3 arias, ya que Scala define Tuple2 y Tuple3 s (entiendo que escalar las funciones de tupla en una aria arbitraria es difícil), pero obtengo el mismo error.

Gracias de antemano por cualquier ayuda ofrecida :).

Respuesta

8

Sólo se desea asignar más de tuplas que tienen los mismos tipos - de lo contrario el mapa no tendría sentido - pero no contiene Tupla que, en su tipo de firma.Pero si usted está dispuesto a hacer un poco de trabajo, puede configurarlo de modo que tuplas funcionan de la manera que ha solicitado:

Fundamentación:

class TupTup6[A,B](a: (A,A,A,A,A,A), b: (B,B,B,B,B,B)) { 
    def op[C](f:(A,B)=>C) = (f(a._1,b._1), f(a._2,b._2), f(a._3,b._3), 
          f(a._4,b._4), f(a._5,b._5), f(a._6,b._6)) 
} 
implicit def enable_tuptup6[A,B](ab: ((A,A,A,A,A,A),(B,B,B,B,B,B))) = { 
    new TupTup6(ab._1,ab._2) 
} 

Uso:

scala> ((1,2,3,4,5,6) , (6,5,4,3,2,1)) op { _ + _ } 
res0: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7) 
+0

Me gusta esto. Necesito investigar las implícitas más, ya que parece que son una construcción muy poderosa que me estoy perdiendo (estoy aprendiendo a Scala como voy actualmente). ¡Gracias! – frio

+0

oh, eso es bastante elegante. ¡Gusta! –

3

Tuple2 # zip no lo ayudará aquí, funciona cuando los elementos contenidos son TraversableLike/IterableLike - que no son Tuples.

Es probable que desea definir su propia función sumRecords que toma dos registros y devuelve su suma:

def sumRecord(a:Record, b:Record) = new Record(
    a._1 + b._1, 
    a._2 + b._2, 
    a._3 + b._3, 
    a._4 + b._4, 
    a._5 + b._5, 
    a._6 + b._6 
) 

entonces para usar con un par [Record, Record]:

val p : Pair[Record, Record] = ... 
val summed = sumRecord(p._1, p._2) 

Claro, hay abstracciones disponibles; pero como Record se va a arreglar a lo largo de su diseño, tienen poco valor.

+0

Esa es una solución razonablemente limpia, supongo. Podría esperar un poco antes de aceptar esta respuesta en caso de que surja algo más en línea con lo que estaba buscando, pero eso definitivamente funcionaría :). Esperaba usar map/sum/zip para hacerlo, ya que eso me parece más limpio. ¿Conoces el razonamiento para no permitir que zip funcione en tuplas? – frio

+0

Se debe a que Tuple2.zipped delega a la colección en _1, y una Tuple no es una colección. En teoría, debería ser posible, pero para casi todos los usos imaginables sería mejor utilizar colecciones adecuadas. –

+1

Es porque Record es fundamental para el diseño, vale la pena insistir en tener un puñado de métodos de utilidad. – IttayD

0

Recibirá el error porque trata la tupla como una colección.

¿Es posible usar listas en lugar de tuplas? Entonces el cálculo es simple:

scala> List(1,2,3,4,5,6).zip(List(1,2,3,4,5,6)).map(x => x._1 + x._2)  
res6: List[Int] = List(2, 4, 6, 8, 10, 12) 
+0

Preferiría no usar listas ('Seq' crea listas de forma predeterminada) porque entonces no puedo verificar en el sistema de tipos que los registros son de longitud 6; Estoy obligado a hacer eso en tiempo de ejecución, que es algo que quiero evitar. Gracias, sin embargo :). – frio

6

Recibí esta pequeña inspiración.

class TupleZipper[T <: Product](t1: T) { 
    private def listify(p: Product) = p.productIterator.toList 
    def zipWith(t2: T) = (listify(t1), listify(t2)).zipped 
} 
implicit def mkZipper[T <: Product](t1: T) = new TupleZipper(t1) 

// ha ha, it's arity magic 
scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2))      
<console>:8: error: type mismatch; 
found : (Int, Int, Int, Int, Int) 
required: (Int, Int, Int, Int, Int, Int) 
     ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2)) 
            ^

scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, 1))     
res1: (List[Any], List[Any])#Zipped[List[Any],Any,List[Any],Any] = [email protected] 

scala> res1 map ((x, y) => x.asInstanceOf[Int] + y.asInstanceOf[Int])  
res2: List[Int] = List(7, 7, 7, 7, 7, 7) 

Sí, un grupo de Anys sale por el otro extremo. No es muy emocionante, pero no mucho de lo que puedes hacer cuando intentas forzarte en Tuples de esta manera.

Edit: oh, y por supuesto, el sistema de tipos te da todo el valor aquí.

scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, "abc"))   
<console>:8: error: type mismatch; 
found : java.lang.String("abc") 
required: Int 
     ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, "abc")) 
                ^
+0

Definitivamente muestra la necesidad de una colección homogénea de tamaño fijo. Ahora bien, si podemos codificar la cantidad de elementos como un número peano en el tipo de colección ... (http://jim-mcbeath.blogspot.com/2008/11/practical-church-numerals-in-scala.html) . ¡Total exageración para este problema! –

5
import scala.collection._ 

type Record = (Int, Int, Int, Int, Int, Int) 

implicit def toIterable(r: Record) = new Iterable[Int]{ 
    def iterator = r.productIterator.asInstanceOf[Iterator[Int]] 
} 

implicit def cbf[From <: Iterable[Int]] = new generic.CanBuildFrom[From, Int, Record] { 
    def apply(from: From) = apply 
    def apply = new mutable.Builder[Int, Record] { 
     var array = Array.ofDim[Int](6) 
     var i = 0 

     def +=(elem: Int) = { 
     array(i) += elem 
     i += 1 
     this 
     } 

     def clear() = i = 0 

     def result() = (array(0), array(1), array(2), array(3), array(4), array(5)) 

    } 
} 

uso:

scala> ((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped.map{_ + _} 
res1: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7) 
3

solución a corto :

type Record = (Int, Int, Int, Int, Int, Int) 

implicit def toList(r: Record) = r.productIterator.asInstanceOf[Iterator[Int]].toList 
implicit def toTuple(l: List[Int]): Record = (l(0), l(1), l(2), l(3), l(4), l(5)) 

uso:

scala> ((1,2,3,4,5,6), (6,5,4,3,2,1)).zipped map {_ + _}: Record 
res0: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7) 
+0

Gracias por esto. Muy corto y limpio. – frio

2

ahora se puede lograr fácilmente esto con shapeless, de esta manera:

import shapeless._ 
import shapeless.syntax.std.tuple._ 

val a = (1, 2, 3, 4, 5, 6) 
val b = (6, 5, 4, 3, 2, 1) 

object sum extends Poly1 { 
    implicit def f = use((t: (Int, Int)) => t._1 + t._2) 
} 

val r = a.zip(b) map sum // r is a (Int, Int, Int, Int, Int, Int) 

El inconveniente es la sintaxis extraña que hay que utilizar para expresar la función sum, pero todo es de tipo seguro y tipo a cuadros .

+0

Me encanta esta solución, porque en un proyecto en evolución, el tamaño de la tupla podría cambiar con el tiempo y el código que se escribió una vez no se verá afectado. –

0

Como actualización de la respuesta de Rex Kerr, a partir de Scala 2.10 puede usar implicit classes: azúcar sintáctico que hace que la solución sea aún más corta.

implicit class TupTup6[A,B](x: ((A,A,A,A,A,A),(B,B,B,B,B,B))) { 
    def op[C](f:(A,B)=>C) = ( 
     f(x._1._1,x._2._1), 
     f(x._1._2,x._2._2), 
     f(x._1._3,x._2._3), 
     f(x._1._4,x._2._4), 
     f(x._1._5,x._2._5), 
     f(x._1._6,x._2._6)) 
}