views:

100

answers:

3

Hi, I would like to map the elements of a Scala tuple (or triple, ...) using a single function returning type R. The result should be a tuple (or triple, ...) with elements of type R.

OK, if the elements of the tuple are from the same type, the mapping is not a problem:

scala> implicit def t2mapper[A](t: (A,A)) = new { def map[R](f: A => R) = (f(t._1),f(t._2)) }
t2mapper: [A](t: (A, A))java.lang.Object{def map[R](f: (A) => R): (R, R)}

scala> (1,2) map (_ + 1)
res0: (Int, Int) = (2,3)

But is it also possible to make this solution generic, i.e. to map tuples that contain elements of different types in the same manner?

Example:

class Super(i: Int)
object Sub1 extends Super(1)
object Sub2 extends Super(2)

(Sub1, Sub2) map (_.i)

should return

(1,2): (Int, Int)

But I could not find a solution so that the mapping function determines the super type of Sub1 and Sub2. I tried to use type boundaries, but my idea failed:

scala> implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }
<console>:8: error: X is already defined as type X
       implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }
                                                                    ^
<console>:8: error: type mismatch;
 found   : A
 required: X
 Note: implicit method t2mapper is not applicable here because it comes after the application point and it lacks an explicit result type
       implicit def t2mapper[A,B](t: (A,B)) = new { def map[X >: A, X >: B, R](f: X => R) = (f(t._1),f(t._2)) }

Here X >: B seems to override X >: A. Does Scala not support type boundaries regarding multiple types? If yes, why not?

+7  A: 

I think this is what you're looking for:

implicit def t2mapper[X, A <: X, B <: X](t: (A,B)) = new {
  def map[R](f: X => R) = (f(t._1), f(t._2))
}

scala> (Sub1, Sub2) map (_.i)                             
res6: (Int, Int) = (1,2)

A more "functional" way to do this would be with 2 separate functions:

implicit def t2mapper[A, B](t: (A, B)) = new { 
  def map[R](f: A => R, g: B => R) = (f(t._1), g(t._2)) 
}       

scala> (1, "hello") map (_ + 1, _.length)                                         
res1: (Int, Int) = (2,5)
pelotom
That's it! Thank you very much!
Stefan Endrullis
+3  A: 

I’m not a scala type genius but maybe this works:

implicit def t2mapper[X, A<:X, B<:X](t: (A,B)) = new { def map[A, B, R](f: X => R) = (f(t._1),f(t._2)) }
Debilski
Thanks! Works. :)
Stefan Endrullis
+3  A: 

The deeper question here is "why are you using a Tuple for this?"

Tuples are hetrogenous by design, and can contain an assortment of very different types. If you want a collection of related things, then you should be using ...drum roll... a collection!

A Set or Sequence will have no impact on performance, and would be a much better fit for this kind of work. After all, that's what they're designed for.

Kevin Wright
For my purposes collections are too flexible because of their variable number of elements. This piece of code is part of an internal DSL written in Scala, where I want to ensure at compile time that the user specifies functions that process exactly 2 arguments (tuples). Furthermore, if I would use collections instead of tuples in my closure definitions, they would get more verbose, since I cannot use pattern matching ("case (a,b)") anymore.
Stefan Endrullis
Regarding pattern matching: You can match on collections: List(1,2,3) match { case List(a,b,c) => ... } .
mkneissl