Scala元组的通用'map'函数是什么?

10

我希望能够使用一个返回类型为R的函数来映射Scala元组(或三元组等)。结果应该是一个具有类型为R的元素的元组(或三元组等)。

如果元组的元素是相同类型的,则映射不是问题:

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)

但是,是否也有可能使此解决方案通用化,即以相同方式映射包含不同类型元素的元组?

示例:

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

(Sub1, Sub2) map (_.i)

应该返回

(1,2): (Int, Int)

但是我找不到解决方案,让映射函数确定Sub1和Sub2的超类。我尝试使用类型边界,但我的想法失败了:

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

这里的X >: B似乎覆盖了X >: A。是否意味着Scala不支持涉及多种类型的类型边界?如果是,为什么?

2
这似乎是HLists的一个案例。例如,请参见http://apocalisp.wordpress.com/2010/10/15/type-level-programming-in-scala-part-6e-hlist%20apply/ - mkneissl
5个回答

13
我想这就是您要寻找的内容:

我认为这正是您所寻求的:

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)

一种更“实用”的方法是使用两个单独的函数来完成:
```html

一个更“实用”的方法是使用两个单独的函数:

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

5

我不是Scala类型的天才,但也许这个可以解决:

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

1
这可以轻松地使用shapeless实现,尽管您必须先定义映射函数,然后再执行映射:
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)

在定义Super时,我必须在i前面添加val,像这样:class Super(val i: Int),这样它就可以在外部访问了。


0

对于两个要应用的函数不相同的情况

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

我提供这个答案的主要原因是它适用于元组列表(只需将 Some 更改为 List 并删除 get)。

0
这里更深入的问题是“为什么要使用 Tuple?”
Tuple 的设计是异构的,可以包含各种非常不同的类型。如果你想要一个相关联的集合,那么你应该使用...鼓点...一个集合!
Set 或 Sequence 不会影响性能,并且更适合这种工作。毕竟,它们就是为此而设计的。

3
为了我的目的,集合由于其元素数量可变而过于灵活。这段代码是Scala语言内部DSL的一部分,我想在编译时确保用户指定处理恰好2个参数(元组)的函数。此外,如果我在闭包定义中使用集合而不是元组,它们会变得更加冗长,因为我不能再使用模式匹配("case (a,b)")。 - Stefan Endrullis
3
关于模式匹配:你可以对集合进行匹配:List(1,2,3) match { case List(a,b,c) => ... }。 - mkneissl
1
@mkneissl:没错,但这样你就失去了关于元素数量的类型安全性:List(1,2,3,4) match { case List(a,b,c) => ... } 在运行时会失败。 - Mechanical snail

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接