写多态函数接受IndexedSeq [A]以及ParVector [A]?

时间:2015-04-12 00:17:18

标签: scala functional-programming polymorphism

我想编写一个接受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的基本内容。有没有人有解决方案(最好有解释?)

感谢。

1 个答案:

答案 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]来解决这个问题。

解压缩类型变量

首先是简单的

A

这只是集合中值的类型,因此对于GenSeqLike[A, Repr],这将是Seq[Int]

REPR

这是集合的基础类型

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用于存储底层集合表示,以帮助通知要返回的集合类型。最后一点如何运作我们还没有解释,所以现在就这样做吧!

CanBuildFrom [-From,-Elem,+ To]

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}}类型执行任何操作,或者在特定情况下执行新的ReprRepr

不幸的是,我们必须手动提供通用参数,否则类型系统会混淆。值得庆幸的是,我们可以再次使用更高级别来避免这种情况,

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值。