编写接受 IndexedSeq[A] 和 ParVector[A] 的多态函数?
write polymorphic function that accept IndexedSeq[A] as well as ParVector[A]?
我想编写一个接受 IndexedSeq[A] 或 ParVector[A] 的多态函数。在函数内部,我想访问前置方法,即 SeqLike 中的 +:
。 SeqLike like 对我来说是一个相当混乱的类型,因为它需要一个 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 的基本知识。有没有人有解决方案(最好对此有解释?)
谢谢。
处理 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
提供正确的值。您还需要强制 GenSeqLike[A, Repr]
中的 Repr
是 B
。否则某些方法不会 return 正确的类型。 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)
以及更高版本
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]
和 IndexedSeq[A]
子类。
话虽如此,让我们来谈谈如何直接使用 GenSeqLike[A, Repr]
来解决这个问题。
解压类型变量
首先是简单的
一个
这只是集合中值的类型,因此对于 Seq[Int]
这将是 Int
。
代表
这是集合的基础类型。
Scala 集合在共同特征中实现了大部分功能,因此它们不必到处重复代码。此外,他们希望允许 out of band 类型像集合一样运行,即使它们不是从集合特征继承的(我在看你 Array
),并允许客户 libraries/programs 非常轻松地添加自己的集合实例,同时获得大量免费定义的集合方法。
它们的设计有两个指导约束
- Return 最具体的操作类型
- 不要违反里氏代换原则(https://en.wikipedia.org/wiki/Liskov_substitution_principle)
注意:这些示例摘自上述文章,并非我自己的。(为了完整起见,此处再次链接http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html)
第一个约束可以在下面的例子中展示。 BitSet
是一组非负整数。如果我执行以下操作,结果应该是什么?
BitSet(1).map(_+1): ???
正确答案是 BitSet
。我知道这看起来很明显,但请考虑以下内容。此操作的类型是什么?
BitSet(1).map(_.toFloat): ???
不可能是BitSet
,对吧?因为我们说过 BitSet
值是非负整数。所以结果是SortedSet[Float]
.
Repr
参数结合适当的 CanBuildFrom
实例(我稍后解释这是什么)是允许 returning 的主要机制之一最具体的类型可能。我们可以通过在 REPL 上欺骗系统来看到这一点。考虑以下内容,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]
所以它会尽可能地尝试 return 该类型。现在看这个,
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
出来。
因此 Scala 集合尝试为您的操作提供最具体的类型,同时仍然允许大量代码重用。他们通过利用 Repr
类型来做到这一点,因为我们作为 CanBuildFrom
(仍在使用它)我知道你可能想知道这与你的问题有什么关系,别担心我们正在现在。我不会对 Liskov 的替换原则说什么,因为它与你的具体问题关系不大(但你仍然应该阅读它!)
好的,现在我们明白 GenSeqLike[A, Repr]
是 scala 集合用来重用 Seq
(以及 Seq
等其他东西)代码的特征。我们了解到 Repr
用于存储底层集合表示,以帮助将集合类型告知 return。我们还没有解释最后一点是如何工作的,所以让我们现在就开始吧!
CanBuildFrom[-From, -Elem, +To]
一个CanBuildFrom
实例是集合库如何知道如何构建给定操作的结果类型。例如 SeqLike[A, Repr]
上 +:
方法的 real 类型是这样的。
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
将在元素 A
上采用类型 Repr
的 GenSeqLike
的子类并构建一个新的Repr
。这意味着我们可以对 Repr
类型执行任何操作,这将导致一个新的 Repr
,或者在特定情况下一个新的 ParVector
或 IndexedSeq
.
不幸的是,我们必须手动提供泛型参数,否则类型系统会变得混乱。谢天谢地,我们可以再次使用更高的种类来避免这种情况,
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
了解 scala 集合的工作原理,但我很难想到我会真正推荐它的用例。该代码难以理解,难以使用,并且很可能有一些我错过的边缘情况。一般来说,我建议尽可能避免与 scala 集合实现特征交互,例如 GenSeqLike
,除非您将自己的集合安装到系统中。您仍然需要轻触 GenSeqLike
以获得 GenSeq
中的所有操作,方法是给它正确的 Repr
类型,但您可以避免考虑 CanBuildFrom
值。
我想编写一个接受 IndexedSeq[A] 或 ParVector[A] 的多态函数。在函数内部,我想访问前置方法,即 SeqLike 中的 +:
。 SeqLike like 对我来说是一个相当混乱的类型,因为它需要一个 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 的基本知识。有没有人有解决方案(最好对此有解释?)
谢谢。
处理 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
提供正确的值。您还需要强制 GenSeqLike[A, Repr]
中的 Repr
是 B
。否则某些方法不会 return 正确的类型。 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)
以及更高版本
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]
和 IndexedSeq[A]
子类。
话虽如此,让我们来谈谈如何直接使用 GenSeqLike[A, Repr]
来解决这个问题。
解压类型变量
首先是简单的
一个
这只是集合中值的类型,因此对于 Seq[Int]
这将是 Int
。
代表
这是集合的基础类型。
Scala 集合在共同特征中实现了大部分功能,因此它们不必到处重复代码。此外,他们希望允许 out of band 类型像集合一样运行,即使它们不是从集合特征继承的(我在看你 Array
),并允许客户 libraries/programs 非常轻松地添加自己的集合实例,同时获得大量免费定义的集合方法。
它们的设计有两个指导约束
- Return 最具体的操作类型
- 不要违反里氏代换原则(https://en.wikipedia.org/wiki/Liskov_substitution_principle)
注意:这些示例摘自上述文章,并非我自己的。(为了完整起见,此处再次链接http://docs.scala-lang.org/overviews/core/architecture-of-scala-collections.html)
第一个约束可以在下面的例子中展示。 BitSet
是一组非负整数。如果我执行以下操作,结果应该是什么?
BitSet(1).map(_+1): ???
正确答案是 BitSet
。我知道这看起来很明显,但请考虑以下内容。此操作的类型是什么?
BitSet(1).map(_.toFloat): ???
不可能是BitSet
,对吧?因为我们说过 BitSet
值是非负整数。所以结果是SortedSet[Float]
.
Repr
参数结合适当的 CanBuildFrom
实例(我稍后解释这是什么)是允许 returning 的主要机制之一最具体的类型可能。我们可以通过在 REPL 上欺骗系统来看到这一点。考虑以下内容,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]
所以它会尽可能地尝试 return 该类型。现在看这个,
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
出来。
因此 Scala 集合尝试为您的操作提供最具体的类型,同时仍然允许大量代码重用。他们通过利用 Repr
类型来做到这一点,因为我们作为 CanBuildFrom
(仍在使用它)我知道你可能想知道这与你的问题有什么关系,别担心我们正在现在。我不会对 Liskov 的替换原则说什么,因为它与你的具体问题关系不大(但你仍然应该阅读它!)
好的,现在我们明白 GenSeqLike[A, Repr]
是 scala 集合用来重用 Seq
(以及 Seq
等其他东西)代码的特征。我们了解到 Repr
用于存储底层集合表示,以帮助将集合类型告知 return。我们还没有解释最后一点是如何工作的,所以让我们现在就开始吧!
CanBuildFrom[-From, -Elem, +To]
一个CanBuildFrom
实例是集合库如何知道如何构建给定操作的结果类型。例如 SeqLike[A, Repr]
上 +:
方法的 real 类型是这样的。
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
将在元素 A
上采用类型 Repr
的 GenSeqLike
的子类并构建一个新的Repr
。这意味着我们可以对 Repr
类型执行任何操作,这将导致一个新的 Repr
,或者在特定情况下一个新的 ParVector
或 IndexedSeq
.
不幸的是,我们必须手动提供泛型参数,否则类型系统会变得混乱。谢天谢地,我们可以再次使用更高的种类来避免这种情况,
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
了解 scala 集合的工作原理,但我很难想到我会真正推荐它的用例。该代码难以理解,难以使用,并且很可能有一些我错过的边缘情况。一般来说,我建议尽可能避免与 scala 集合实现特征交互,例如 GenSeqLike
,除非您将自己的集合安装到系统中。您仍然需要轻触 GenSeqLike
以获得 GenSeq
中的所有操作,方法是给它正确的 Repr
类型,但您可以避免考虑 CanBuildFrom
值。