我可以“拉皮条我的图书馆"吗?与具有很好的变体类型的TraversableLike.map类似物? [英] Can I "pimp my library" with an analogue of TraversableLike.map that has nicely variant types?

查看:81
本文介绍了我可以“拉皮条我的图书馆"吗?与具有很好的变体类型的TraversableLike.map类似物?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Suppose I want to add functionality like map to a Scala List, something along the lines of list mapmap f, which applies the function f to each element of list twice. (A more serious example might be implementing a parallel or distributed map, but I don't want to get distracted by details in that direction.)

我的第一个方法是

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)

当然只限于List,并且没有理由我们不希望它对任何具有map功能的Traverseable起作用,例如Set s或Stream s.所以第二次尝试看起来像

except of course it's only for Lists, and there's no reason we shouldn't want this to work for anything Traverseable, with a map function, e.g. Sets or Streams. So the second attempt looks like

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]:

But now, of course, the result can't be assigned to 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的所有子类中,并成功返回该类型的对象吗?

Is there some middle ground? Can I write an implicit conversion that adds a method to all subclasses of Traversable, and successfully returns objects with that type?

(我想这涉及了解可怕的CanBuildFrom特征,甚至可能是breakout!)

(I'm guess this involves understanding the dreaded CanBuildFrom trait, and maybe even breakout!)

推荐答案

您不能对所有Traversable执行此操作,因为它们不能保证地图返回的内容比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.

UPDATE I've added another pimped method, mapToString, to demonstrate why TraversableW accepts two type parameters, rather than one parameter as in Alexey's solution. The parameter CC is a higher kinded type, it represents the container type of the original collection. The second parameter, A, represents the element type of the original collection. The method mapToString is thus able to return the original container type with a different element type: CC[String.

更新2 感谢@oxbow_lakes评论,我对此进行了重新思考.确实有可能直接拉皮条CC[X] <: Traversable[X],而并非严格要求TraversableLike.内联评论:

UPDATE 2 Thanks to @oxbow_lakes comment, I've rethought this. It is indeed possible to directly pimp CC[X] <: Traversable[X], TraversableLike is not strictly needed. Comments inline:

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]恢复特定的集合子类型.

What's the difference? We had to use collection.breakOut, because we can't recover the specific collection subtype from a mere 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
}

Builder b用原始集合初始化,这是通过map保留动态类型的机制.但是,我们的CanBuildFrom通过类型参数Nothing拒绝了 From 的所有知识.您只能使用Nothing忽略它,而这正是breakOut的作用:

The Builder b is initialized with the original collection, which is the mechanism to preserve the dynamic type through a map. However, our CanBuildFrom disavowed all knowledge of the From, by way of the type argument Nothing. All you can do with Nothing is ignore it, which is exactly what breakOut does:

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.

We can't call b.apply(from), no more than you could call def foo(a: Nothing) = 0.

这篇关于我可以“拉皮条我的图书馆"吗?与具有很好的变体类型的TraversableLike.map类似物?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆