使用scala集合 - CanBuildFrom麻烦 [英] Working with scala collections - CanBuildFrom trouble
问题描述
我想写一个方法,接受任何类型的集合 CC [_]
并将其映射到一个新的集合(相同的集合类型,但不同的元素类型)和我正在奋斗皇家。基本上我试图实现 map
但不是在集合本身。
问题
我试图实现一个签名的方法,看起来有点像:
def map [CC [_],T,U](cct:CC [T],f:T => U):CC [U]
它的用法是:
map (List(1,2,3,4),(_:Int).toString)//将返回List [String]
我感兴趣的答案,也可以在 CC
是 Array
对我的尝试(下面)最终失败的原因感兴趣。
我的尝试
(对于不耐烦的,在下面的内容中,我完全没有得到这个工作。重申,问题方法?)
我这样开始:
scala> def [C,C] [C]:CC [T],U,CC [U] U] =
| cct map f
^
< console>:9:错误:值映射不是类型参数CC [T]的成员
cct映射f
^
好,我需要说的是 CC
是可遍历的!
scala> def int [T,U,X,CC [X] <:Traversable [X]](cct:CC [T],f:T => U)(隐含cbf:CanBuildFrom [CC [T] CC [U]]):CC [U] =
| cct map f
< console>:10:error:type mismatch;
found:Traversable [U]
必需:CC [U]
cct map f
^
Err,OK!也许如果我实际上指定 cbf
实例。毕竟,它将返回类型( To
)指定为 CC [U]
:
scala> def int [T,U,X,CC [X] <:Traversable [X]](cct:CC [T],f:T => U)(隐含cbf:CanBuildFrom [CC [T] 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]]
必需:scala.collection.generic.CanBuildFrom [Traversable [T],U,CC [U ]]
cct.map(t => f(t))(cbf)
^
b $ b
Err,OK!这是一个更具体的错误。看起来我可以使用它!
scala> def implicit [T,U,X,CC [X] <:Traversable [X]](cct:CC [T],f:T => U)(隐式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] :T => U)(隐含cbf:scala.collection.generic.CanBuildFrom [Traversable [T],U,CC [U]])CC [U]
辉煌。我有一个地图
!让我们使用这个东西!
scala> map(List(1,2,3,4),(_:Int).toString)
< console>:11:错误:无法构造类型为List [java.lang.String]的类型java.lang.String基于类型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]}
define trait Functor
scala> def map [F [_]:Functor,A,B](fa:F [A],f:A => B) )
map:[F [_],A,B](fa:F [A],f:A => B)(隐含证据$ 1:Functor [F])F [B]
scala> map(List(1,2,3,4),(_:Int).toString)
< console>:12:error:找不到类型Functor的evidence参数的隐式值
map(List(1,2,3,4),(_:Int).toString)
^
因此
scala>隐含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)
自我备忘:听Tony!
CanBuildFrom 本身,或者Array / code>与
Seq
问题。您正在使用String
,这是不是较高类型,但支持map
Char
。
SO:首先是Scala的集合设计。
是一种推断集合类型的方法(例如
String
,Array [Int]
,列表[Foo]
)和元素类型(例如Char
,Int
,Foo
对应于上述)。
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)(隐含fr:IsTraversableOnce [Repr]):FilterMapImpl [fr.A,Repr] =
new FilterMapImpl(fr.conversion(r))
FIRST,使用集合的类需要两个类型参数:集合的特定类型
Repr
和元素的类型A
。
接下来,定义一个只接受集合类型
Repr
的隐式方法。您使用IsTraversableOnce
(注意:还有一个IsTraversableLike
)捕获该集合的元素类型。你看到这在类型签名FilterMapImpl [Repr,fr.A]
中使用。
现在,部分原因是因为Scala并没有对所有类似于函子的操作使用相同的类别。具体来说,
map
是String
的有用方法。我可以调整所有的字符。但是,String
只能是Seq [Char]
。如果我想定义一个Functor
,那么我的类别只能包含Char
类型和Char => Char
。这个逻辑在CanBuildFrom
中捕获。但是,由于String
是Seq [Char]
,如果您尝试使用在
Seq
的的
方法支持的类别中 c> CanBuildFrom
将改变您对map
的调用。
我们的类别的继承关系。如果您尝试使用
Functor
模式,我们将类型签名放到我们可以保留的最具体类别。称它为你会的;
b现在,因为我们试图同时推断出很多类型,我认为这个选项有最少的类型注释:
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>地图(HI)适用(_ + 1 toChar)
警告:有2个功能警告;使用-feature重新运行细节
res5:String = IJ
注意这里是
选项2 >IsTraversableLike
捕获从Repr
到TraversableLike
我们还将该方法调用了一点,以便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 $ b $注意,我们必须使用Repr与TraversableLike [A,Repr] $ c> $ c>。
无论如何,现在让我们看看在
Traversable
:scala> map(List(40,41))(_ + 1 toChar)
警告:有1个功能警告;使用-feature查看详细信息
res8:List [Char] = List(),*)
$ b b太棒了。但是,如果我们想要
Array
和String
使用相同的用法,我们必须多做一些工作: / p>
scala> map(Array('H','I'):IndexedSeq [Char])(_ + 1 toChar)(breakOut):Array [Char]
警告: re-run with -feature for details
res14:Array [Char] = Array(I,J)
scala> map(HI:Seq [Char])(_ + 1 toChar)(breakOut):String
警告:使用-feature重新运行细节
res11:String = IJ
到这个用法:
- 我们必须使用类型注释来隐式转换
String
/数组
→Seq
/IndexedSeq
li>
- 我们必须使用
breakOut
为我们的CanBuildFrom
这是因为Repr< ;: TraversableLike [A,Repr]
不包括String
或Array
,因为那些使用隐式转换。
选项3
您可以将所有含义放在一起,并要求用户注释类型。不是最优雅的解决方案,所以我想避免发布,除非你真的想看到它。
基本上如果你想包括
String
和Array [T]
作为集合,你必须跳过一些圈。地图的类别限制适用于Scala中的String
和BitSet
functors。
我希望有所帮助。如果您还有其他问题,请给我打电话。
I'm trying to write a method which accepts any type of collection
CC[_]
and maps it to a new collection (the same collection type but a different element type) and I am struggling royally. Basically I'm trying to implementmap
but not on the collection itself.The Question
I'm trying to implement a method with a signature which looks a bit like:
def map[CC[_], T, U](cct: CC[T], f: T => U): CC[U]
It's usage would be:
map(List(1, 2, 3, 4), (_ : Int).toString) //would return List[String]
I'm interested in an answer which would also work where
CC
isArray
and I'm interested in the reason my attempts (below) have ultimately not worked.
My Attempts
(For the impatient, in what follows, I utterly fail to get this to work. To reiterate, the question is "how can I write such a method?")
I start like this:
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 ^
OK, that makes sense - I need to say that
CC
is traversable!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 ^
Err, OK! Maybe if I actually specify that
cbf
instance. After all, it specifies the return type (To
) asCC[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) ^
Err, OK! That's a more specific error. Looks like I can use that!
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]
Brilliant. I has me a
map
! Let's use this thing!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) ^
Say, what?
Observations
I really can't help but think that Tony Morris' observations about this at the time were absolutely spot on. What did he say? He said "Whatever that is, it is not map". Look at how easy this is in 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]
Then
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) ^
So that
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)
Memo to self: listen to Tony!
解决方案What you're running into is not necessarily
CanBuildFrom
itself, or theArray
vs.Seq
issue. You're running intoString
which is not higher-kinded, but supportsmap
against itsChar
s.SO: First a digression into Scala's collection design.
What you need is a way to infer both the collection type (e.g.
String
,Array[Int]
,List[Foo]
) and the element type (e.g.Char
,Int
,Foo
corresponding to the above).Scala 2.10.x has added a few "type classes" to help you. For example, you can do the following:
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))
There's two pieces here. FIRST, your class that uses collections needs two type parameters: The specific type of the collection
Repr
and the type of the elementsA
.Next, you define an implicit method which only takes the collection type
Repr
. You use theIsTraversableOnce
(note: there is also anIsTraversableLike
) to capture the element type of that collection. You see this used in the type signatureFilterMapImpl[Repr, fr.A]
.Now, part of this is because Scala does not use the same category for all of its "functor-like" operations. Specifically,
map
is a useful method forString
. I can adjust all characters. However,String
can only be aSeq[Char]
. If I want to define aFunctor
, then my category can only contain the typeChar
and the arrowsChar => Char
. This logic is captured inCanBuildFrom
. However, since aString
is aSeq[Char]
, if you try to use amap
in the category supported bySeq
'smap
method, thenCanBuildFrom
will alter your call tomap
.We're essentially defining an "inheritance" relationship for our categories. If you try to use the
Functor
pattern, we drop the type signature to the most specific category we can retain. Call it what you will; that's a big motivating factor for the current collection design.End Digression, answer the question
Now, because we're trying to infer a lot of types at the same time, I think this option has the fewest type annotations:
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
The important piece to note here is that
IsTraversableLike
captures a conversion fromRepr
toTraversableLike
that allows you to use themap
method.Option 2
We also split the method call up a bit so that Scala can infer the types
Repr
andU
before we define our anonymous function. To avoid type annotations on anonymous functions, we must have all types known before it shows up. Now, we can still have Scala infer some types, but lose things that are implicitlyTraversable
if we do this: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
Notice that we have to use
Repr with TraversableLike[A,Repr]
. It seems that most F-bounded types require this juggling.In any case, now let's see what happens on something that extends
Traversable
:scala> map(List(40,41))(_ + 1 toChar ) warning: there were 1 feature warnings; re-run with -feature for details res8: List[Char] = List(), *)
That's great. However, if we want the same usage for
Array
andString
, we have to go to a bit more work: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
There are two pieces to this usage:
- We have to use a type annotation for the implicit conversion from
String
/Array
→Seq
/IndexedSeq
.- We have to use
breakOut
for ourCanBuildFrom
and type-annotate the expected return value.This is solely because the type
Repr <: TraversableLike[A,Repr]
does not includeString
orArray
, since those use implicit conversions.Option 3
You can place all the implicits together at the end and require the user to annotate types. Not the most elegant solution, so I think I'll avoid posting it unless you'd really like to see it.
SO, basically if you want to include
String
andArray[T]
as collections, you have to jump through some hoops. This category restriction for map applies to bothString
andBitSet
functors in Scala.I hope that helps. Ping me if you have any more questions.
这篇关于使用scala集合 - CanBuildFrom麻烦的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
查看全文