我想编写一个接受IndexedSeq [A]或ParVector [A]的多态函数。在函数内部,我想访问preq方法,即SeqLike中的+:
。 SeqLike喜欢对我来说是一个相当令人困惑的类型,因为它需要Repr
我忽略了,当然没有成功。
def goFoo[M[_] <: SeqLike[_,_], A](ac: M[A])(p: Int): M[A] = ???
该函数应该接受一个空的累加器来启动并递归调用p次,每次都加前A
。这是一个具体的例子
def goStripper[M[_] <: SeqLike[_,_]](ac: M[PDFTextStripper])(p: Int): M[PDFTextStripper] = {
val str = new PDFTextStripper
str.setStartPage(p)
str.setEndPage(p)
if (p > 1) goStripper(str +: ac)(p-1)
else str +: ac
}
但当然这并没有编译,因为我遗漏了一些关于SeqLike的基本内容。有没有人有解决方案(最好有解释?)
感谢。
答案 0 :(得分:3)
有时处理SeqLike[A, Repr]
可能有点困难。您真的需要很好地理解集合库的工作原理(如果您感兴趣,这是一篇很棒的文章,http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html)。值得庆幸的是,在您的情况下,您实际上并不需要担心太多。 IndexedSeq[A]
和ParVector[A]
都是scala.collection.GenSeq[A]
的子类。所以你只能按如下方式编写方法
scala> def goFoo[A, B <: GenSeq[A] with GenSeqLike[A, B]](ac: B)(p: Int): B = ac
goFoo: [A, B <: scala.collection.GenSeq[A] with scala.collection.GenSeqLike[A,B]](ac: B)(p: Int)B
scala> goFoo[Int, IndexedSeq[Int]](IndexedSeq(1))(1)
res26: IndexedSeq[Int] = Vector(1)
scala> goFoo[Int, ParVector[Int]](new ParVector(Vector(1)))(1)
res27: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1)
您需要强制B
既是GenSeq[A]
和GenSeqLike[A, Repr]
的子类型,也可以为Repr
提供正确的值。您还需要强制Repr
中的GenSeqLike[A, Repr]
为B
。否则,某些方法不会返回正确的类型。 Repr
是集合的底层表示。要真正理解它,您应该阅读我链接的文章,但您可以将其视为许多集合操作的输出类型,尽管这非常简单。如果您对非常感兴趣,我会在下面详细讨论。现在,我们可以说它们与我们正在运行的集合的类型相同。
现在,类型系统需要您手动提供两个通用参数,这很好,但我们可以做得更好。如果允许更高的种类,你可以使它更清洁。
scala> import scala.language.higherKinds
import scala.language.higherKinds
scala> def goFoo[A, B[A] <: GenSeq[A] with GenSeqLike[A, B[A]]](ac: B[A])(p: Int): B[A] = ac
goFoo: [A, B[A] <: scala.collection.GenSeq[A] with scala.collection.GenSeqLike[A,B[A]]](ac: B[A])(p: Int)B[A]
scala> goFoo(IndexedSeq(1))(1)
res28: IndexedSeq[Int] = Vector(1)
scala> goFoo(new ParVector(Vector(1)))(1)
res29: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1)
现在您不必担心手动提供类型。
这些解决方案也适用于递归。
scala> @tailrec
| def goFoo[A, B <: GenSeq[A] with GenSeqLike[A, B]](ac: B)(p: Int): B =
| if(p == 0){
| ac
| } else {
| goFoo[A, B](ac.drop(1))(p-1)
| }
goFoo: [A, B <: scala.collection.GenSeq[A] with scala.collection.GenSeqLike[A,B]](ac: B)(p: Int)B
scala> goFoo[Int, IndexedSeq[Int]](IndexedSeq(1, 2))(1)
res30: IndexedSeq[Int] = Vector(2)
和更高的kinded版本
scala> @tailrec
| def goFoo[A, B[A] <: GenSeq[A] with GenSeqLike[A, B[A]]](ac: B[A])(p: Int): B[A] =
| if(p == 0){
| ac
| } else {
| goFoo(ac.drop(1))(p-1)
| }
goFoo: [A, B[A] <: scala.collection.GenSeq[A] with scala.collection.GenSeqLike[A,B[A]]](ac: B[A])(p: Int)B[A]
scala> goFoo(IndexedSeq(1, 2))(1)
res31: IndexedSeq[Int] = Vector(2)
GenSeqLike[A, Repr]
直接TL; DR 所以我只想说,除非你需要一个更通用的解决方案,否则不要这样做。这是最难理解和使用的。我们无法使用SeqLike[A, Repr]
,因为ParVector
不是SeqLike
的实例,但我们可以使用GenSeqLike[A, Repr]
,ParVector[A]
和{{1}子类。
话虽如此,让我们谈谈如何直接使用IndexedSeq[A]
来解决这个问题。
首先是简单的
这只是集合中值的类型,因此对于GenSeqLike[A, Repr]
,这将是Seq[Int]
。
这是集合的基础类型。
Scala集合在共同特征中实现了大部分功能,因此他们不必在整个地方重复代码。此外,他们希望允许带外类型的功能就像它们是集合一样,即使它们不是从集合特征中继承(我正在看着你{{1}并且允许客户端库/程序非常容易地添加自己的集合实例,同时获得大量免费定义的集合方法。
它们设计有两个引导约束
注意:这些示例来自上述文章,而不是我自己的。(此处再次链接为完整性http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html)
第一个约束可以在以下示例中显示。 Int
是一组非负整数。如果我执行以下操作,结果应该是什么?
Array
正确答案是BitSet
。我知道这似乎很明显,但请考虑以下几点。这个操作的类型是什么?
BitSet(1).map(_+1): ???
不能成为BitSet
,对吗?因为我们说BitSet(1).map(_.toFloat): ???
值是非负整数。结果证明是BitSet
。
BitSet
参数与适当的SortedSet[Float]
实例相结合(我在一秒钟内解释这是什么)是允许返回最具体类型的主要机制之一。我们可以通过在REPL上欺骗系统来看到这一点。请考虑以下情况,Repr
既是CanBuildFrom
又是Vector
的子类。那么如果我们这样做呢......
IndexedSeq
查看此处的最终类型Seq
。这是因为我们告诉类型系统集合的底层表示是scala> val x: GenSeqLike[Int, IndexedSeq[Int]] = Vector(1)
x: scala.collection.SeqLike[Int,IndexedSeq[Int]] = Vector(1)
scala> 1 +: x
res26: IndexedSeq[Int] = Vector(1, 1)
所以如果可能的话它会尝试返回该类型。现在看这个,
IndexedSeq[Int]
现在我们得到一个IndexedSeq[Int]
。
因此,scala集合尝试为您的操作提供最具体的类型,同时仍允许大量代码重用。他们通过利用scala> val x: GenSeqLike[Int, Seq[Int]] = Vector(1)
x: scala.collection.SeqLike[Int,Seq[Int]] = Vector(1)
scala> 1 +: x
res27: Seq[Int] = Vector(1, 1)
类型来实现这一点,因为我们只是Seq
(仍然可以使用它)我知道你可能想知道这与你的问题有什么关系,不要担心我们&# 39;现在重新开始。我不打算就利斯科夫的替代原则说什么,因为它与你的具体问题没什么关系(但你仍然应该读到它!)
好的,现在我们知道Repr
是scala集合用于重用CanBuildFrom
代码(以及GenSeqLike[A, Repr]
等其他内容)的特性。我们知道Seq
用于存储底层集合表示,以帮助通知要返回的集合类型。最后一点如何运作我们还没有解释,所以现在就这样做吧!
Seq
实例是集合库如何知道如何来构建给定操作的结果类型。例如,Repr
上的CanBuildFrom
方法的真实类型就是这个。
+:
这意味着为了将元素添加到SeqLike[A, Repr]
,我们需要一个 abstract def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That
的实例,其中GenSeqLike[A, Repr]
是我们当前集合的类型,CanBuildFrom[Repr, B, That]
是一个我们在集合中拥有的元素的超类型,Repr
是我们在操作完成后将拥有的集合类型。我不打算进入B
如何工作的内部(再次查看链接文章的详细信息),现在只要相信我这是它的作用。
现在我们已准备好构建一个与That
值一致的CanBuildFrom
实例。
goFoo
我们在这里说的是,有一个GenSeqLike[A, Repr]
将scala> def goFoo[A, Repr <: GenSeqLike[A, Repr]](ac: Repr)(p: Int)(implicit cbf: CanBuildFrom[Repr, A, Repr]): Repr = ac
goFoo: [A, Repr <: scala.collection.GenSeqLike[A,Repr]](ac: Repr)(p: Int)(implicit cbf: scala.collection.generic.CanBuildFrom[Repr,A,Repr])Repr
scala> goFoo[Int, IndexedSeq[Int]](IndexedSeq(1))(1)
res7: IndexedSeq[Int] = Vector(1)
scala> goFoo[Int, ParVector[Int]](new ParVector(Vector(1)))(1)
res8: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1)
类型CanBuildFrom
的子类放在元素GenSeqLike
上并构建一个新的Repr
1}}。这意味着我们可以对导致新A
的{{1}}类型执行任何操作,或者在特定情况下执行新的Repr
或Repr
。
不幸的是,我们必须手动提供通用参数,否则类型系统会混淆。值得庆幸的是,我们可以再次使用更高级别来避免这种情况,
Repr
所以这很好,因为它比使用ParVector
更通用,但它也方式更令人困惑。除了思考实验之外,我不建议这样做。
虽然有必要了解scala集合如何直接使用IndexedSeq
,但我很难想到一个用例,我实际上会推荐它。代码很难理解,难以使用,并且很可能有一些我错过的边缘情况。一般来说,我建议尽可能避免与scala集合实现特征(例如scala> def goFoo[A, Repr[A] <: GenSeqLike[A, Repr[A]]](ac: Repr[A])(p: Int)(implicit cbf: CanBuildFrom[Repr[A], A, Repr[A]]): Repr[A] = ac
goFoo: [A, Repr[A] <: scala.collection.GenSeqLike[A,Repr[A]]](ac: Repr[A])(p: Int)(implicit cbf: scala.collection.generic.CanBuildFrom[Repr[A],A,Repr[A]])Repr[A]
scala> goFoo(IndexedSeq(1))(1)
res16: IndexedSeq[Int] = Vector(1)
scala> goFoo(new ParVector(Vector(1)))(1)
res17: scala.collection.parallel.immutable.ParVector[Int] = ParVector(1)
)进行交互,除非您将自己的集合安装到系统中。您仍然需要轻轻触摸GenSeq
才能通过为GenSeqLike
提供正确的GenSeqLike
类型来完成GenSeqLike
中的所有操作,但您可以避免考虑GenSeq
值。