44

'map' preserves the number of elements, so using it on a Tuple seems sensible.

My attempts so far:

scala> (3,4).map(_*2)    
error: value map is not a member of (Int, Int)
       (3,4).map(_*2)
             ^
scala> (3,4).productIterator.map(_*2)
error: value * is not a member of Any
       (3,4).productIterator.map(_*2)
                                  ^
scala> (3,4).productIterator.map(_.asInstanceOf[Int]*2)
res4: Iterator[Int] = non-empty iterator

scala> (3,4).productIterator.map(_.asInstanceOf[Int]*2).toList
res5: List[Int] = List(6, 8)

It looks quite painful... And I haven't even begun to try to convert it back to a tuple.
Am I doing it wrong? Could the library be improved?

erip
  • 16,374
  • 11
  • 66
  • 121
Eldritch Conundrum
  • 8,452
  • 6
  • 42
  • 50
  • It looks like you're using a tuple where you really ought to be using a collection. Consider using a real collection class instead - tuples should not be used as a kind of collections. – Jesper Apr 30 '10 at 18:47
  • 1
    @Jesper: I disagree: I might simply want to apply the same operation DRY'ly and consicely on a collection of item whose size is statically known. – Erik Kaplun Mar 08 '14 at 05:16

3 Answers3

36

In general, the element types of a tuple aren't the same, so map doesn't make sense. You can define a function to handle the special case, though:

scala> def map[A, B](as: (A, A))(f: A => B) = 
     as match { case (a1, a2) => (f(a1), f(a2)) } 
map: [A,B](as: (A, A))(f: (A) => B)(B, B)

scala> val p = (1, 2)    
p: (Int, Int) = (1,2)

scala> map(p){ _ * 2 }
res1: (Int, Int) = (2,4)

You could use the Pimp My Library pattern to call this as p.map(_ * 2).

UPDATE

Even when the types of the elements are not the same, Tuple2[A, B] is a Bifunctor, which can be mapped with the bimap operation.

scala> import scalaz._
import scalaz._

scala> import Scalaz._
import Scalaz._

scala> val f = (_: Int) * 2
f: (Int) => Int = <function1>

scala> val g = (_: String) * 2
g: (String) => String = <function1>

scala> f <-: (1, "1") :-> g
res12: (Int, String) = (2,11)

UPDATE 2

http://gist.github.com/454818

retronym
  • 54,768
  • 12
  • 155
  • 168
  • For the record, I'm changing the accepted answer from this to the more recent answer about the Shapeless library, which wasn't available back in 2010. – Eldritch Conundrum Oct 28 '13 at 12:40
30

shapeless Supports mapping and folding over tuples via an intermediary HList representation,

Sample REPL session,

scala> import shapeless._ ; import Tuples._
import shapeless._
import Tuples._

scala> object double extends (Int -> Int) (_*2)
defined module double

scala> (3, 4).hlisted.map(double).tupled
res0: (Int, Int) = (6,8)

Where the elements of the tuple are of different types you can map with a polymorphic function with type-specific cases,

scala> object frob extends Poly1 {
     |   implicit def caseInt     = at[Int](_*2)
     |   implicit def caseString  = at[String]("!"+_+"!")
     |   implicit def caseBoolean = at[Boolean](!_)
     | }
defined module frob

scala> (23, "foo", false, "bar", 13).hlisted.map(frob).tupled
res1: (Int, String, Boolean, String, Int) = (46,!foo!,true,!bar!,26)

Update

As of shapeless 2.0.0-M1 mapping over tuples is supported directly. The above examples now look like this,

scala> import shapeless._, poly._, syntax.std.tuple._
import shapeless._
import poly._
import syntax.std.tuple._

scala> object double extends (Int -> Int) (_*2)
defined module double

scala> (3, 4) map double
res0: (Int, Int) = (6,8)

scala> object frob extends Poly1 {
     |   implicit def caseInt     = at[Int](_*2)
     |   implicit def caseString  = at[String]("!"+_+"!")
     |   implicit def caseBoolean = at[Boolean](!_)
     | }
defined module frob

scala> (23, "foo", false, "bar", 13) map frob
res1: (Int, String, Boolean, String, Int) = (46,!foo!,true,!bar!,26)
Miles Sabin
  • 23,015
  • 6
  • 61
  • 95
  • This is great! I would mark your answer as accepted, but I don't know if it's ok to change the accepted answer after several years. – Eldritch Conundrum Oct 25 '13 at 15:13
  • 2
    You should accept whichever answer you think is best. If the circumstances or your opinion changes over time, then I think it's reasonable to update your acceptance accordingly. – Miles Sabin Oct 27 '13 at 12:21
  • IMHO, this stuff is way too abstract. I mean, it needs to import 3 whole namespaces (import xxxxx._) and for the full generic case, it needs as many additional implicits as item type in the tuples. It certainly depends on use cases, but most probably that a simple, explicit code would be more readable without loss of efficiency. – Juh_ Apr 25 '16 at 13:03
  • 1
    `object double extends (Int -> Int) (_*2)` causes my IDE to freak out. WTF is this line doing? – Michael Lafayette Jul 01 '16 at 19:24
1

map function gets an A => B and returns F[B].

def map[A, B](f: A => B) : F[B]

As retronym wrote Tuple2[A, B] is a Bifunctor, so you can look for the bimap function in scalaz or cats.
bimap is a function that maps both sides of the tuple:

def bimap[A, B, C, D](fa: A => C, fb: B => D): Tuple2[C, D]

Because Tuple[A, B] holds 2 values and only one value can be mapped (by convention the right value), you can just return the same value for the left side and use the right function to map over the right value of the tuple.

(3, 4).bimap(identity, _ * 2)
Sagi
  • 8,972
  • 3
  • 33
  • 41