我正在尝试编写一个方法,它接受任何类型的集合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)
^
说,什么?
我真的情不自禁地认为Tony Morris当时对此的观察是绝对的。他说什么?他说“不管是什么,它不是地图”。看看这在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)
回忆自己:听托尼!
答案 0 :(得分:23)
您遇到的问题不一定是CanBuildFrom
本身,也不一定是Array
与Seq
问题。您已经遇到String
更高的角色,但支持map
对其Char
。
SO:首先是对Scala集合设计的偏离。
您需要的是一种推断集合类型(例如String
,Array[Int]
,List[Foo]
)和元素类型(例如Char
,{{1 }},Int
对应于上述内容。
Scala 2.10.x添加了一些“类型类”来帮助您。例如,您可以执行以下操作:
Foo
这里有两件。首先,使用集合的类需要两个类型参数:集合的特定类型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
中看到此内容。
现在,部分原因是因为Scala不会对其所有类似“仿函数”的操作使用相同的类别。具体而言,FilterMapImpl[Repr, fr.A]
是map
的有用方法。我可以调整所有角色。但是,String
只能是String
。如果我想定义Seq[Char]
,那么我的类别只能包含Functor
类型和箭头Char
。该逻辑在Char => Char
中捕获。但是,由于CanBuildFrom
是String
,如果您尝试在Seq[Char]
的{{1}}方法支持的类别中使用map
,那么{{ 1}}会改变您对Seq
的调用。
我们实质上是为我们的类别定义“继承”关系。如果您尝试使用map
模式,我们会将类型签名删除到我们可以保留的最具体的类别。称之为你的意思;这对当前的收藏设计来说是一个很大的激励因素。
结束弃儿,回答问题
现在,因为我们试图同时推断类型的 lot ,我认为此选项具有最少的类型注释:
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
方法。
选项2
我们还将方法调用分开一点,以便在我们定义匿名函数之前,Scala可以推断类型Repr
和TraversableLike
。为避免匿名函数上的类型注释,我们必须在显示之前将所有类型已知。现在,我们仍然可以让Scala推断某些类型,但如果我们这样做,则会丢失隐式 map
的内容:
Repr
请注意,我们必须使用U
。似乎大多数F-bounded类型都需要这种杂耍。
在任何情况下,现在让我们看看在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]
和Traversable
使用相同的用法,我们还需要做更多的工作:
scala> map(List(40,41))(_ + 1 toChar )
warning: there were 1 feature warnings; re-run with -feature for details
res8: List[Char] = List(), *)
此用法分为两部分:
Array
/ String
→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
/ String
。Array
使用Seq
并输入预期返回值。这完全是因为IndexedSeq
类型不包含breakOut
或CanBuildFrom
,因为它们使用隐式转化。
选项3
您可以将所有隐含放在最后,并要求用户注释类型。不是最优雅的解决方案,所以我想我会避免发布它,除非你真的想看到它。
所以,基本上如果你想把Repr <: TraversableLike[A,Repr]
和String
作为集合,你必须跳过一些箍。此地图的类别限制适用于Scala中的Array
和String
仿函数。
我希望有所帮助。如果你还有其他问题,请告诉我。
答案 1 :(得分: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)
^
这个不能编译,因为CanBuildFrom
中的隐式Traversable
被硬编码为仅接受Traversable
作为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
。但是存在从Array
到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指出的那样,2.10中的精彩IsTraversableLike
更适合这一点。
答案 2 :(得分: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)