Question

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?

Was it helpful?

Solution

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)

OTHER TIPS

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

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.

For the case when the two functions to be applied are not the same

scala> Some((1, "hello")).map((((_: Int) + 1 -> (_: String).length)).tupled).get
res112: (Int, Int) = (2,5)

The main reason I have supplied this answer is it works for lists of tuples (just change Some to List and remove the get).

This can easily be achieved using shapeless, although you'll have to define the mapping function first before doing the map:

object fun extends Poly1 {
  implicit def value[S <: Super] = at[S](_.i) 
}

(Sub1, Sub2) map fun // typed as (Int, Int), and indeed equal to (1, 2)

(I had to add a val in front of i in the definition of Super, this way: class Super(val i: Int), so that it can be accessed outside)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top