tags:

views:

207

answers:

6

I want to do something like this (simplified quite heavily):

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

Ignore the actual values of the integers (although it's important that these are 6-tuples, actually :)). Essentially, I want to use this fairly regularly in a function which maintains a Map[String, (Int, Int, Int, Int, Int, Int)] when an existing element is updated.

As it is, Scala spits this out at 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

If I use Seqs instead of tuples, everything works fine, but I want to enforce an arity of 6 in the type system (I'll probably type Record = (Int, Int, Int, Int, Int, Int) as a quick refactor shortly).

Can anyone offer some advice on what I'm doing wrong/why Scala won't deal with the code above? I thought it might work if I used a 2- or 3-arity tuple, seeing as Scala defines Tuple2 and Tuple3s (I understand that scaling tuple functions across an arbitrary n-arity is difficult), but I get the same error.

Thanks in advance for any help offered :).

+3  A: 

Tuple2#zipped won't help you out here, it works when the contained elements are TraversableLike/IterableLike - which Tuples aren't.

You'll probably want to define your own sumRecords function that takes two Records and returns their sum:

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
)

Then to use it with a Pair[Record, Record]:

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

Sure, there are abstractions available; but as Record is going to be fixed throughout your design, then they have little value.

Kevin Wright
That's a reasonably clean solution, I guess. I might wait a bit before accepting this answer in case something more in-line with what I was gunning for comes up, but that would definitely work :). I was hoping to just use map/sum/zip to do it, as that seems cleaner to me. Do you know the reasoning for not allowing zip to work on tuples?
frio
It's because Tuple2.zipped delegates to the collection in _1, and a Tuple isn't a collection. It should be possible in theory, but for almost every imaginable use you'd be better off using proper collections.
Kevin Wright
It's because Record is central to the design that it is worth insisting on having a handful of utility method.
IttayD
Ah, I see Kevin, that makes sense. I'm still learning Scala, so some of the syntax is still reasonably alien to me (hence, trying to look at the source isn't much help, sadly). Thanks!
frio
A: 

You get the error because you treat the tuple as a collection.

Is it possible for you to use lists instead of tuples? Then the calculation is 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)
olle kullberg
I'd prefer not to use lists (`Seq` creates lists by default) because then I can't check in the type system that the records are of length 6; I'm forced to do that at runtime which is something I want to avoid. Thanks though :).
frio
+5  A: 

I received this little inspiration.

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] = scala.Tuple2$Zipped@42e934e

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

Yes, a bunch of Anys comes out the other end. Not real thrilling but not a lot you can do when you try to force yourself on Tuples this way.

Edit: oh, and of course the type system gives you the full monty here.

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"))
                                                     ^
extempore
It definitely shows the need for a fixed-size homogeneous collection. Now if we can just encode the number of elements as a peano number in the collection type... (http://jim-mcbeath.blogspot.com/2008/11/practical-church-numerals-in-scala.html). Total overkill for this problem though!
Kevin Wright
+5  A: 
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))

    }
}

usage:

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)
IttayD
+4  A: 

You only want to map over tuples which have identical types--otherwise the map wouldn't make sense--but Tuple doesn't contain that in its type signature. But if you're willing to do a little work, you can set it up so that tuples work the way you requested:

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)
Rex Kerr
I like this. I need to look into implicits more, as it looks like they're a very powerful construct which I'm missing out on (I'm learning Scala as I go currently). Thanks!
frio
oh, that's rather elegant. Like it!
Kevin Wright
+1  A: 

short solution:

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

usage:

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)
Stefan Endrullis
Thanks for this. Very short and clean.
frio