【问题标题】:General 'map' function for Scala tuples?Scala 元组的通用“地图”功能?
【发布时间】:2011-04-30 15:53:51
【问题描述】:

我想使用返回类型 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 &gt;: B 似乎覆盖了X &gt;: A。 Scala 不支持关于多种类型的类型边界吗?如果是,为什么不呢?

【问题讨论】:

标签: scala tuples contravariance superclass


【解决方案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),这样就可以在外面访问了)

【讨论】:

    【解决方案2】:

    对于两个要应用的功能不一样的情况

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

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

    【讨论】:

      【解决方案3】:

      这里更深层次的问题是“你为什么要为此使用元组?”

      元组在设计上是异构的,可以包含各种不同的类型。如果你想要一个相关的东西的集合,那么你应该使用 ...drum roll... 一个集合!

      Set 或 Sequence 对性能没有影响,并且更适合这种工作。毕竟,它们就是为此而设计的。

      【讨论】:

      • 出于我的目的,集合太灵活了,因为它们的元素数量可变。这段代码是用 Scala 编写的内部 DSL 的一部分,我想在编译时确保用户指定的函数恰好处理 2 个参数(元组)。此外,如果我在闭包定义中使用集合而不是元组,它们会变得更加冗长,因为我不能再使用模式匹配(“case (a,b)”)了。
      • 关于模式匹配:您可以在集合上进行匹配:List(1,2,3) match { case List(a,b,c) => ... } .
      • @mkneissl: 是的,但是你失去了元素数量的类型安全性:List(1,2,3,4) match { case List(a,b,c) =&gt; ... } 在运行时失败。
      【解决方案4】:

      我想这就是你要找的东西:

      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)
      

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-01-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-20
          • 2017-06-05
          • 2014-06-26
          相关资源
          最近更新 更多