21

我正在尝试编写一个接受任何类型集合CC[_]并将其映射到新集合(相同的集合类型但不同的元素类型)的方法,我正在努力挣扎。基本上我正在尝试实施map,但不是在集合本身上

问题

我正在尝试实现一个带有签名的方法,它看起来有点像:

def map[CC[_], T, U](cct: CC[T], f: T => U): CC[U]

它的用法是:

map(List(1, 2, 3, 4), (_ : Int).toString) //would return List[String]

我对一个也可以在哪里起作用的答案感兴趣,CC并且Array我对我的尝试(如下)最终没有奏效的原因感兴趣。


我的尝试

(对于不耐烦的人,在接下来的内容中,我完全无法让它发挥作用。重申一下,问题是“我怎么能写出这样的方法?”)

我是这样开始的:

scala> def map[T, U, CC[_]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
                                                             ^
 <console>:9: error: value map is not a member of type parameter CC[T]
       cct map f
           ^

好的,这是有道理的 - 我需要说这CC是可遍历的!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct map f
<console>:10: error: type mismatch;
 found   : Traversable[U]
 required: CC[U]
       cct map f
           ^

错,好!也许如果我真的指定了那个cbf实例。毕竟,它将返回类型 ( To) 指定为CC[U]

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

错,好!这是一个更具体的错误。看来我可以用!

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
     | cct.map(t => f(t))(cbf)
map: [T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]])CC[U]

杰出的。我有我一个map!让我们用这个东西!

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:11: error: Cannot construct a collection of type List[java.lang.String] with elements of type java.lang.String based on a collection of type Traversable[Int].
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

说什么?


观察

我真的不禁认为托尼莫里斯当时对此的观察绝对是正确的。他说什么?他说“不管是什么,它都不是地图”。看看这在 scalaz-style 中是多么容易

scala> trait Functor[F[_]] { def fmap[A, B](fa: F[A])(f: A => B): F[B] }
defined trait Functor

scala> def map[F[_]: Functor, A, B](fa: F[A], f: A => B): F[B] = implicitly[Functor[F]].fmap(fa)(f)
map: [F[_], A, B](fa: F[A], f: A => B)(implicit evidence$1: Functor[F])F[B]

然后

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
<console>:12: error: could not find implicit value for evidence parameter of type Functor[List]
              map(List(1, 2, 3, 4), (_ : Int).toString)
                 ^

以便

scala> implicit val ListFunctor = new Functor[List] { def fmap[A, B](fa: List[A])(f: A => B) = fa map f }
ListFunctor: java.lang.Object with Functor[List] = $anon$1@4395cbcb

scala> map(List(1, 2, 3, 4), (_ : Int).toString)
res5: List[java.lang.String] = List(1, 2, 3, 4)

给自己的备忘录:听托尼的!

4

3 回答 3

23

您遇到的不一定是CanBuildFrom它本身,也不一定是Arrayvs.Seq问题。您遇到String不是高种,而是支持mapChar的 s。

SO:首先是对 Scala 集合设计的题外话。

您需要的是一种推断集合类型(例如String, Array[Int]List[Foo]和元素类型(例如Char, IntFoo对应于上述内容)的方法。

Scala 2.10.x 添加了一些“类型类”来帮助您。例如,您可以执行以下操作:

class FilterMapImpl[A, Repr](val r: GenTraversableLike[A, Repr]) {
  final def filterMap[B, That](f: A => Option[B])(implicit cbf: CanBuildFrom[Repr, B, That]): That =
    r.flatMap(f(_).toSeq)
 }
 implicit def filterMap[Repr, A](r: Repr)(implicit fr: IsTraversableOnce[Repr]): FilterMapImpl[fr.A,Repr] =
   new FilterMapImpl(fr.conversion(r))

这里有两块。首先,使用集合的类需要两个类型参数:集合的特定类型Repr和元素的类型A

接下来,您定义一个仅采用集合类型的隐式方法Repr。您使用IsTraversableOnce(注意:还有一个IsTraversableLike) 来捕获该集合的元素类型。您会在类型签名中看到它FilterMapImpl[Repr, fr.A]

现在,部分原因是因为 Scala 没有对所有“类仿函数”操作使用相同的类别。具体来说,map是一个有用的方法String。我可以调整所有字符。但是,String只能是一个Seq[Char]。如果我想定义一个Functor,那么我的类别只能包含类型Char和箭头Char => Char。此逻辑在CanBuildFrom. 但是,由于 aString是 a Seq[Char],如果您尝试在's方法map支持的类别中使用 a ,那么将更改您对.SeqmapCanBuildFrommap

我们本质上是在为我们的类别定义一种“继承”关系。如果您尝试使用该Functor模式,我们会将类型签名放到我们可以保留的最具体的类别中。随心所欲地称呼它;这是当前系列设计的一大动力。

结束题外话,回答问题

现在,因为我们试图同时推断出很多类型,我认为这个选项的类型注释最少:

import collection.generic._

def map[Repr](col: Repr)(implicit tr: IsTraversableLike[Repr]) = new {
  def apply[U, That](f: tr.A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    tr.conversion(col) map f
}


scala> map("HI") apply (_ + 1 toChar )
warning: there were 2 feature warnings; re-run with -feature for details
res5: String = IJ

这里要注意的重要一点是IsTraversableLike捕获从Repr到的转换TraversableLike,使您可以使用该map方法。

选项 2

我们还对方法调用进行了一些拆分,以便 Scala 可以在定义匿名函数之前Repr推断类型。U为了避免匿名函数上的类型注释,我们必须在它出现之前知道所有类型。现在,我们仍然可以让 Scala 推断某些类型,但是如果我们这样做,就会丢失隐含的东西: Traversable

import collection.generic._
import collection._
def map[Repr <: TraversableLike[A, Repr], A, U, That](col: Repr with TraversableLike[A,Repr])(f: A => U)(implicit cbf: CanBuildFrom[Repr, U, That]) = 
    col map f

请注意,我们必须使用Repr with TraversableLike[A,Repr]. 似乎大多数 F 有界类型都需要这种杂耍。

无论如何,现在让我们看看扩展的东西会发生什么Traversable

scala> map(List(40,41))(_ + 1 toChar )
warning: there were 1 feature warnings; re-run with -feature for details
res8: List[Char] = List(), *)

那太棒了。但是,如果我们想要和 相同的用法ArrayString我们必须做更多的工作:

scala> map(Array('H', 'I'): IndexedSeq[Char])(_ + 1 toChar)(breakOut): Array[Char]
warning: there were 1 feature warnings; re-run with -feature for details
res14: Array[Char] = Array(I, J)

scala> map("HI": Seq[Char])(_ + 1 toChar)(breakOut) : String
warning: there were 1 feature warnings; re-run with -feature for details
res11: String = IJ

这种用法有两个部分:

  1. 我们必须使用类型注释来进行String/ ArraySeq/的隐式转换IndexedSeq
  2. 我们必须使用breakOut我们的CanBuildFrom和类型注释的预期返回值。

这仅仅是因为类型Repr <: TraversableLike[A,Repr]不包括Stringor Array,因为它们使用隐式转换。

选项 3

您可以将所有隐式放在最后,并要求用户注释类型。不是最优雅的解决方案,所以我想我会避免发布它,除非你真的很想看到它。

所以,基本上如果你想包含StringArray[T]作为集合,你必须跳过一些箍。map 的这个类别限制适用于 Scala 中 的String和函子。BitSet

我希望这会有所帮助。如果您还有任何问题,请联系我。

于 2012-08-31T13:53:40.263 回答
11

其实里面有几个问题...

让我们从您的最后一次尝试开始:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[Traversable[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)

这个确实可以编译但不起作用,因为根据您的类型签名,它必须寻找一个隐含CanBuildFrom[Traversable[Int], String, List[String]]的范围,而没有一个。如果您要手动创建一个,它会起作用。

现在之前的尝试:

scala> def map[T, U, X, CC[X] <: Traversable[X]](cct: CC[T], f: T => U)
 (implicit cbf: CanBuildFrom[CC[T], U, CC[U]]): CC[U] = 
  cct.map(t => f(t))(cbf)
<console>:10: error: type mismatch;
 found   : scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]]
 required: scala.collection.generic.CanBuildFrom[Traversable[T],U,CC[U]]
       cct.map(t => f(t))(cbf)
                          ^

这个不能编译,因为隐式CanBuildFrominTraversable被硬编码为只接受 aTraversable作为From集合。但是,正如在另一个答案中指出的那样,TraversableLike知道实际的集合类型(它是它的第二个类型参数),所以它map用正确的方式定义CanBuildFrom[CC[T], U, CC[U]]并且每个人都很高兴。实际上,TraversableLike从 继承这个map方法scala.collection.generic.FilterMonadic,所以这更通用:

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T] <: FilterMonadic[T, CC[T]]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T] <: scala.collection.generic.FilterMonadic[T,CC[T]]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

最后,上面的内容不适用于数组,因为Array不是FilterMonadic. 但是有一个从Arrayto的隐式转换ArrayOps,后者实现了FilterMonadic。因此,如果您在其中添加一个绑定视图,您也会得到适用于数组的东西:

scala> import scala.collection.generic._
import scala.collection.generic._

scala> def map[T, U, CC[T]](cct: CC[T], f: T => U)
 |  (implicit cbf:  CanBuildFrom[CC[T], U, CC[U]], 
 |   ev: CC[T] => FilterMonadic[T,CC[T]]): CC[U] = cct.map(f)
warning: there were 1 feature warnings; re-run with -feature for details
map: [T, U, CC[T]](cct: CC[T], f: T => U)(implicit cbf: scala.collection.generic.CanBuildFrom[CC[T],U,CC[U]], implicit ev: CC[T] => scala.collection.generic.FilterMonadic[T,CC[T]])CC[U]

scala> map(List(1,2,3,4), (_:Int).toString + "k")
res0: List[String] = List(1k, 2k, 3k, 4k)

scala> map(Array(1,2,3,4), (_:Int).toString + "k")
res1: Array[String] = Array(1k, 2k, 3k, 4k)

编辑: 还有一种方法可以使它适用于String和 co:只需删除输入/输出集合中的较高类型,在中间使用第三个:

def map[T, U, From, To, Middle](cct: From, f: T => U)
 (implicit ev: From => FilterMonadic[T, Middle], 
  cbf: CanBuildFrom[Middle,U,To]): To = cct.map(f)

这适用于String甚至适用于Map[A,B]

scala> map(Array(42,1,2), (_:Int).toString)
res0: Array[java.lang.String] = Array(42, 1, 2)

scala> map(List(42,1,2), (_:Int).toString)
res1: List[java.lang.String] = List(42, 1, 2)

scala> map("abcdef", (x: Char) => (x + 1).toChar)
res2: String = bcdefg

scala> map(Map(1 -> "a", 2 -> "b", 42 -> "hi!"), (a:(Int, String)) => (a._2, a._1))
res5: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, hi! -> 42)

用 2.9.2 测试。但正如 jsuereth 所指出的,IsTraversableLike2.10 中的精彩之处更适合于此。

于 2012-08-30T23:05:22.433 回答
8

是这个吗?

def map[A,B,T[X] <: TraversableLike[X,T[X]]]
  (xs: T[A])(f: A => B)(implicit cbf: CanBuildFrom[T[A],B,T[B]]): T[B] = xs.map(f)

map(List(1,2,3))(_.toString)
// List[String] = List(1, 2, 3)

另请参阅此问题

于 2012-08-30T20:57:57.523 回答