2010-10-12 39 views
6

voglio fare qualcosa di simile (semplificato piuttosto pesantemente):Lavorare con tuple a Scala

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

ignorare i valori effettivi degli interi (anche se è importante che questi sono 6-tuple, in realtà :)) . Essenzialmente, voglio usarlo abbastanza regolarmente in una funzione che mantiene uno Map[String, (Int, Int, Int, Int, Int, Int)] quando un elemento esistente viene aggiornato.

Così com'è, Scala sputa questo fuori di me:

<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 

Se uso Seq s invece di tuple, tutto funziona bene, ma voglio far rispettare un arity di 6 nel sistema di tipo (I Probabilmente sarà type Record = (Int, Int, Int, Int, Int, Int) un veloce refactoring a breve).

Qualcuno può offrire qualche consiglio su cosa sto facendo male/perché Scala non si occuperà del codice sopra? Ho pensato che avrebbe funzionato se avessi usato una tupla 2 o 3-arity, visto che Scala definisce Tuple2 e Tuple3 s (ho capito che scalare le funzioni di tupla attraverso una n-arità arbitraria è difficile), ma ottengo lo stesso errore.

Grazie in anticipo per qualsiasi aiuto offerto :).

risposta

8

si desidera solo per mappare più di tuple che hanno tipi identici - altrimenti la carta non avrebbe senso - ma Tuple non contiene che nel il suo tipo di firma.Ma se siete disposti a fare un po 'di lavoro, è possibile impostarlo in modo che le tuple lavorare nel modo che avete richiesto:

Groundwork:

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

Usage:

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

Mi piace. Ho bisogno di esaminare più implicitamente, in quanto sembra che siano un costrutto molto potente che mi sto perdendo (sto imparando Scala come vado al momento). Grazie! – frio

+0

oh, è piuttosto elegante. Mi piace! –

3

Tuple2 # zippato non ti aiuterà qui, funziona quando gli elementi contenuti sono TraversableLike/IterableLike - quali non sono le tuple.

Probabilmente vuole definire la propria funzione sumRecords che prende due record e restituisce la loro somma:

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 
) 

quindi di utilizzare con un paio [Record, Record]:

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

Certo, ci sono delle astrazioni disponibili; ma visto che Record verrà sistemato durante il tuo progetto, allora hanno poco valore.

+0

Questa è una soluzione abbastanza pulita, immagino. Potrei aspettare un po 'prima di accettare questa risposta nel caso in cui qualcosa di più in linea con quello per cui stavo sparando, ma che funzionerebbe sicuramente :). Speravo di usare solo map/sum/zip per farlo, dato che mi sembra più pulito. Conoscete il ragionamento per non consentire a zip di lavorare su tuple? – frio

+0

È perché Tuple2.zipped ha delegato alla raccolta in _1 e una tupla non è una raccolta. Dovrebbe essere possibile in teoria, ma per quasi ogni uso immaginabile staresti meglio usando le collezioni appropriate. –

+1

È perché Record è centrale nel design che vale la pena insistere per avere una manciata di metodo di utilità. – IttayD

0

Si ottiene l'errore perché si considera la tupla come una raccolta.

È possibile utilizzare elenchi anziché tuple? Poi il calcolo è semplice:

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

Preferirei non utilizzare le liste ('Seq' crea gli elenchi per impostazione predefinita) perché quindi non riesco a verificare nel sistema di tipi che i record siano di lunghezza 6; Sono costretto a farlo in fase di esecuzione che è qualcosa che voglio evitare. Grazie comunque :). – frio

6

Ho ricevuto questo po 'di ispirazione.

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 po 'di Anys viene fuori l'altra estremità. Non molto elettrizzante ma non molto da fare quando cerchi di forzarti su Tuples in questo modo.

Modifica: oh, e, naturalmente, il sistema di tipi ti dà il completo monty qui.

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

Indica chiaramente la necessità di una raccolta omogenea a dimensione fissa. Ora se possiamo solo codificare il numero di elementi come un numero peano nel tipo di raccolta ... (http://jim-mcbeath.blogspot.com/2008/11/practical-church-numerals-in-scala.html) . Overkill totale per questo problema però! –

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

    } 
} 

utilizzo:

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

breve soluzione :

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

utilizzo:

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

Grazie per questo. Molto breve e pulito. – frio

2

È ora possibile raggiungere questo obiettivo con shapeless, in questo modo:

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) 

Lo svantaggio è la sintassi strana è necessario utilizzare per esprimere la funzione sum, ma tutto è type-safe e tipo-controllato .

+0

Amo questa soluzione, perché in un progetto in evoluzione la dimensione della tupla potrebbe essere cambiata nel tempo e il codice scritto una volta non viene modificato. –

0

Come aggiornamento alla risposta di Rex Kerr, a partire da Scala 2.10 è possibile utilizzare implicit classes: zucchero sintattico che rende la soluzione ancora più breve.

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