【问题标题】:Working with scala collections - CanBuildFrom trouble使用 scala 集合 - CanBuildFrom 麻烦
【发布时间】:2012-08-25 15:40:40
【问题描述】:

我正在尝试编写一个方法来接受任何类型的集合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]

我对在CCArray 的情况下也适用的答案感兴趣,并且我对我的尝试(如下)最终没有成功的原因感兴趣。


我的尝试

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

我是这样开始的:

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 风格中是多么容易

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)

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

【问题讨论】:

  • 那么问题是什么?
  • 问题是——我怎样才能写出这样的方法?我们中的一些人试图表明我们已经努力自己回答这个问题!我为 tl;dr 集添加了一个编辑
  • ...而我如此接近。已经在推特上回答了。我会等着看它是否在这里出现......
  • TraversableOncemap 不是函子映射,但话又说回来,它从未假装是函子映射。它的设计目标明确排除了它成为函子映射。
  • 丹尼尔:我不否认。我只是指出,使用它非常难以处理。坦率地说,我应该能够自己回答这个问题

标签: scala collections


【解决方案1】:

是这个吗?

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)

另见this question

【讨论】:

  • 如果您能解释为什么使用 TraversableLike 而不是 Traversable 在这里有效,我很感兴趣,我也对适用于数组的答案感兴趣
  • @oxbow_lakes Traversable 方法生成 Traversable 集合。 TraversableLike 生成 like Traversable 的集合——这是它的第二个参数。当有人说List[T] extends Traversable[T] with TraversableLike[T, List[T]] 时,这意味着您可以将List[T] 用作Traversable[T],但您也可以让它返回List[T] 作为结果,如果合适的话。
  • @oxbow_lakes 现在,对于Array,这要困难得多——实际上,您需要创建单独的方法来处理StringArray,或者不适用于不支持的集合取类型参数,如StringBitSet。或者,请参阅在 Scala 2.10 上创建的类型类以简化这一切——在我的 Scala 2.10.0 演示文稿中提到过。
【解决方案2】:

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

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

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

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

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。但是有一个从ArrayArrayOps的隐式转换,后者实现了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 指出的那样,2.10 中的 IsTraversableLike 更适合这个。

【讨论】:

  • FilterMonadic 确实打算与withFilter 方法一起使用。巧妙的把戏。您正在使用 FilterMonadic 跳过我们在 Scala 2.10 中添加 IsTraversableOnce 的相同类型障碍。
  • @jsuereth 谢谢,我不知道IsTraversableOnceIsTraversableLike,这是一个非常好的补充。我编辑了我的答案以使用 Scala 获得相同的结果
  • 我希望你不介意 - 我正在切换接受的答案,因为它很可爱并且是最新的 2.10 和 IsTraversableLike 加上类型成员真的很整洁。跨度>
【解决方案3】:

您遇到的不一定是CanBuildFrom 本身,也不一定是ArraySeq 的问题。您遇到了String,它不是更高级别的,但支持map 反对它的Chars。

SO:首先离题一下 Scala 的集合设计。

你需要的是一种方法来推断集合类型(例如StringArray[Int]List[Foo])和元素类型(例如CharIntFoo对应于上述)。

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 并未对所有“类函子”操作使用相同的类别。具体来说,mapString 的有用方法。我可以调整所有字符。但是,String 只能是 Seq[Char]。如果我想定义一个Functor,那么我的类别只能包含Char 类型和箭头Char =&gt; Char。此逻辑在CanBuildFrom 中捕获。但是,由于StringSeq[Char],如果您尝试在Seqmap 方法支持的类别中使用map,那么CanBuildFrom 会将您的调用更改为map

我们实质上是在为我们的类别定义一种“继承”关系。如果您尝试使用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 捕获了从ReprTraversableLike 的转换,这允许您使用map 方法。

选项 2

我们还对方法调用进行了一些拆分,以便 Scala 可以在定义匿名函数之前推断出 ReprU 类型。为了避免匿名函数上的类型注释,我们必须在它出现之前让所有类型已知。现在,我们仍然可以让 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 &lt;: TraversableLike[A,Repr] 类型不包括StringArray,因为它们使用隐式转换。

选项 3

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

所以,基本上如果你想将StringArray[T] 包含在集合中,你必须跳过一些障碍。 map 的此类别限制适用于 Scala 中的 StringBitSet 函子。

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

【讨论】:

  • 如果将它添加到字符串会使一般解决方案不太理想,因为它确实是一个不同的问题,为什么不为字符串创建一次性映射并允许转换为 List[Char] 来处理任何复杂的问题?那你就不用再担心字符串大小写了。
  • BitSet 和 String 都有有限的类别。部分问题在于,在 scala 中,您可以在需要 Seq[Char] 的地方传递一个 String,在需要 Set[Int] 的地方传递一个 BitSet。一方面它是隐含的(这会损害解决方案),另一方面它是继承,这会强制您对地图进行签名。所有的东西结合起来创造你所看到的。
猜你喜欢
  • 1970-01-01
  • 2011-11-22
  • 1970-01-01
  • 1970-01-01
  • 2012-05-30
  • 1970-01-01
  • 1970-01-01
  • 2014-07-19
  • 1970-01-01
相关资源
最近更新 更多