【问题标题】:Can I "pimp my library" with an analogue of TraversableLike.map that has nicely variant types?我可以用具有很好变体类型的 TraversableLike.map 的类似物“拉皮条我的图书馆”吗?
【发布时间】:2010-07-12 03:26:16
【问题描述】:

假设我想将map 之类的功能添加到Scala List,类似于list mapmap f,它将函数f 两次应用于list 的每个元素。 (一个更严重的例子可能是实现一个并行或分布式地图,但我不想被那个方向的细节分散注意力。)

我的第一种方法是

object MapMap {
    implicit def createFancyList[A](list: List[A]) = new Object {
        def mapmap(f: A => A): List[A] = { list map { a: A => f(f(a)) } }
    }
}

现在效果很好

scala> import MapMap._
import MapMap._

scala> List(1,2,3) mapmap { _ + 1 }
res1: List[Int] = List(3, 4, 5)

当然它只适用于Lists,我们没有理由不希望它适用于任何Traverseable,具有map 函数,例如Sets 或 Streams。所以第二次尝试看起来像

object MapMap2 {
    implicit def createFancyTraversable[A](t: Traversable[A]) = new Object {
        def mapmap(f: A => A): Traversable[A] = { t map { a: A => f(f(a)) } }
    }
}

但是现在,当然不能将结果分配给List[A]

scala> import MapMap2._
import MapMap2._

scala> val r: List[Int] = List(1,2,3) mapmap { _ + 1 }
<console>:9: error: type mismatch;
 found   : Traversable[Int]
 required: List[Int]

有中间立场吗?能不能写一个隐式转换,给 Traversable 的所有子类添加一个方法,并成功返回该类型的对象?

(我猜这需要理解可怕的 CanBuildFrom 特征,甚至可能是 breakout!)

【问题讨论】:

    标签: scala scala-collections implicit-conversion variance


    【解决方案1】:

    您不能对所有 Traversable 执行此操作,因为它们不保证 map 返回比 Traversable 更具体的任何内容。 请参阅下面的更新 2。

    import collection.generic.CanBuildFrom
    import collection.TraversableLike
    
    class TraversableW[CC[X] <: TraversableLike[X, CC[X]], A](value: CC[A]) {
      def mapmap(f: A => A)(implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): CC[A] 
          = value.map(f andThen f)
      def mapToString(implicit cbf: CanBuildFrom[CC[A], String, CC[String]]): CC[String]
          = value.map(_.toString)
    }
    
    object TraversableW {
      implicit def TraversableWTo[CC[X] <: TraversableLike[X, CC[X]], A](t: CC[A]): TraversableW[CC, A] 
          = new TraversableW[CC, A](t)
    }
    
    locally {
      import TraversableW._
    
      List(1).mapmap(1+)
      List(1).mapToString
      // The static type of Seq is preserved, *and* the dynamic type of List is also
      // preserved.
      assert((List(1): Seq[Int]).mapmap(1+) == List(3))
    }
    

    更新 我添加了另一种拉皮条方法mapToString,以演示为什么TraversableW 接受两个类型参数,而不是像Alexey 的解决方案中的一个参数。参数CC 是更高种类的类型,它代表原始集合的容器类型。第二个参数A,代表原始集合的元素类型。因此,mapToString 方法能够返回具有不同元素类型的原始容器类型:CC[String

    更新 2 感谢@oxbow_lakes 评论,我重新考虑了这一点。确实可以直接拉皮条CC[X] &lt;: Traversable[X]TraversableLike并不是严格需要的。内嵌评论:

    import collection.generic.CanBuildFrom
    import collection.TraversableLike
    
    class TraversableW[CC[X] <: Traversable[X], A](value: CC[A]) {
      /**
       * A CanBuildFromInstance based purely the target element type `Elem`
       * and the target container type `CC`. This can be converted to a
       * `CanBuildFrom[Source, Elem, CC[Elem]` for any type `Source` by
       * `collection.breakOut`.
       */
      type CanBuildTo[Elem, CC[X]] = CanBuildFrom[Nothing, Elem, CC[Elem]]
    
      /**
       * `value` is _only_ known to be a `Traversable[A]`. This in turn
       * turn extends `TraversableLike[A, Traversable[A]]`. The signature
       * of `TraversableLike#map` requires an implicit `CanBuildFrom[Traversable[A], B, That]`,
       * specifically in the call below `CanBuildFrom[Traversable[A], A CC[A]`.
       *
       * Essentially, the specific type of the source collection is not known in the signature
       * of `map`.
       *
       * This cannot be directly found instead we look up a `CanBuildTo[A, CC[A]]` and
       * convert it with `collection.breakOut`
       *
       * In the first example that referenced `TraversableLike[A, CC[A]]`, `map` required a
       * `CanBuildFrom[CC[A], A, CC[A]]` which could be found.
       */
      def mapmap(f: A => A)(implicit cbf: CanBuildTo[A, CC]): CC[A]
          = value.map[A, CC[A]](f andThen f)(collection.breakOut)
      def mapToString(implicit cbf: CanBuildTo[String, CC]): CC[String]
          = value.map[String, CC[String]](_.toString)(collection.breakOut)
    }
    
    object TraversableW {
      implicit def TraversableWTo[CC[X] <: Traversable[X], A](t: CC[A]): TraversableW[CC, A]
          = new TraversableW[CC, A](t)
    }
    
    locally {
      import TraversableW._
    
      assert((List(1)).mapmap(1+) == List(3))
    
      // The static type of `Seq` has been preserved, but the dynamic type of `List` was lost.
      // This is a penalty for using `collection.breakOut`. 
      assert((List(1): Seq[Int]).mapmap(1+) == Seq(3))   
    }
    

    有什么区别?我们不得不使用collection.breakOut,因为我们无法从仅仅Traversable[A] 中恢复特定的集合子类型。

    def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      val b = bf(repr)
      b.sizeHint(this) 
      for (x <- this) b += f(x)
      b.result
    }
    

    Builderb 使用原始集合进行初始化,这是通过map 保留动态类型的机制。然而,我们的CanBuildFrom 通过类型参数Nothing 否认了From 的所有知识。你可以用Nothing 做的就是忽略它,这正是breakOut 所做的:

    def breakOut[From, T, To](implicit b : CanBuildFrom[Nothing, T, To]) =
      new CanBuildFrom[From, T, To] {
        def apply(from: From) = b.apply();
        def apply() = b.apply()
      }
    

    我们不能打电话给b.apply(from),就像你不能打电话给def foo(a: Nothing) = 0一样。

    【讨论】:

    【解决方案2】:

    一般来说,当你想返回相同类型的对象时,你需要TraversableLikeIterableLikeSeqLike等)而不是Traversable。这是我能想到的最通用的版本(单独的 FancyTraversable 类是为了避免推断结构类型和反射命中):

    class FancyTraversable[A, S <: TraversableLike[A, S]](t: S) {
      def mapmap(f: A => A)(implicit bf: CanBuildFrom[S,A,S]): S = { t map { a: A => f(f(a)) } }
    }
    
    implicit def createFancyTraversable[A, S <: TraversableLike[A, S]](t: S): FancyTraversable[A, S] = new FancyTraversable(t)
    

    【讨论】:

    • 我必须导入一些东西吗?我收到“错误:未找到:类型 TraversableLike”。 (2.8.0RC7)
    • "import scala.collection._" 和 "import scala.collection.generic._" 至少可以编译,但现在 "List(1,2,3) mapmap { _ + 1 } ” 只是给了我“错误:值映射图不是 List[Int] 的成员”。
    猜你喜欢
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 2012-11-29
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    相关资源
    最近更新 更多