Use functional combinators on Scala Tuples?

2020-01-27 03:47发布

问题:

'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?

回答1:

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)


回答2:

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



回答3:

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)