有时,我发现自己希望scala集合包含一些缺失的功能,而且它很容易扩展"集合,并提供自定义方法。
从头开始构建集合时,这有点困难。
考虑有用的方法,例如.iterate
。
我将使用类似,熟悉的函数演示用例:unfold
。
unfold
是从初始状态z: S
构造集合的方法,以及用于生成下一个状态的可选元组和元素E
或空的函数表示结束的选项。
方法签名,对于某些集合类型Coll[T]
应该看起来大致如下:
def unfold[S,E](z: S)(f: S ⇒ Option[(S,E)]): Coll[E]
现在,IMO,最自然的"用法应该是,例如:
val state: S = ??? // initial state
val arr: Array[E] = Array.unfold(state){ s ⇒
// code to convert s to some Option[(S,E)]
???
}
对于特定的集合类型,这非常简单:
implicit class ArrayOps(arrObj: Array.type) {
def unfold[S,E : ClassTag](z: S)(f: S => Option[(S,E)]): Array[E] = {
val b = Array.newBuilder[E]
var s = f(z)
while(s.isDefined) {
val Some((state,element)) = s
b += element
s = f(state)
}
b.result()
}
}
在范围内使用这个隐式类,我们可以像这样为Fibonacci seq生成一个数组:
val arr: Array[Int] = Array.unfold(0->1) {
case (a,b) if a < 256 => Some((b -> (a+b)) -> a)
case _ => None
}
但是,如果我们想要将此功能提供给所有其他集合类型,我认为除了C&amp; P代码之外别无其他选择,并将所有Array
次出现替换为List
,{{1}等等#39; ...
所以我尝试了另一种方法:
Seq
现在,在上述范围内,所有需要都是正确类型的导入:
trait BuilderProvider[Elem,Coll] {
def builder: mutable.Builder[Elem,Coll]
}
object BuilderProvider {
object Implicits {
implicit def arrayBuilderProvider[Elem : ClassTag] = new BuilderProvider[Elem,Array[Elem]] {
def builder = Array.newBuilder[Elem]
}
implicit def listBuilderProvider[Elem : ClassTag] = new BuilderProvider[Elem,List[Elem]] {
def builder = List.newBuilder[Elem]
}
// many more logicless implicits
}
}
def unfold[Coll,S,E : ClassTag](z: S)(f: S => Option[(S,E)])(implicit bp: BuilderProvider[E,Coll]): Coll = {
val b = bp.builder
var s = f(z)
while(s.isDefined) {
val Some((state,element)) = s
b += element
s = f(state)
}
b.result()
}
但这也不是正确的。我不想强迫用户导入一些东西,更不用说一个隐式方法,它会在每个方法调用上创建一个无用的布线类。而且,没有简单的方法可以覆盖默认逻辑。您可以考虑诸如import BuilderProvider.Implicits.arrayBuilderProvider
val arr: Array[Int] = unfold(0->1) {
case (a,b) if a < 256 => Some((b -> (a+b)) -> a)
case _ => None
}
之类的集合,最适合懒惰地创建集合,或者考虑其他集合的其他特殊实现细节。
我能提出的最佳解决方案是使用第一个解决方案作为模板,并使用sbt生成源:
Stream
但是这个解决方案存在其他问题,很难维护。试想一下,如果sourceGenerators in Compile += Def.task {
val file = (sourceManaged in Compile).value / "myextensions" / "util" / "collections" / "package.scala"
val colls = Seq("Array","List","Seq","Vector","Set") //etc'...
val prefix = s"""package myextensions.util
|
|package object collections {
|
""".stripMargin
val all = colls.map{ coll =>
s"""
|implicit class ${coll}Ops[Elem](obj: ${coll}.type) {
| def unfold[S,E : ClassTag](z: S)(f: S => Option[(S,E)]): ${coll}[E] = {
| val b = ${coll}.newBuilder[E]
| var s = f(z)
| while(s.isDefined) {
| val Some((state,element)) = s
| b += element
| s = f(state)
| }
| b.result()
| }
|}
""".stripMargin
}
IO.write(file,all.mkString(prefix,"\n","\n}\n"))
Seq(file)
}.taskValue
不是全局添加的唯一函数,那么覆盖默认实现仍然很难。最重要的是,这很难维护,并且不会感觉到&#34;对了。
那么,有没有更好的方法来实现这一目标?
答案 0 :(得分:2)
首先,让我们做一个函数的基本实现,它使用一个显式的Builder
参数。如果展开,它可能看起来像这样:
import scala.language.higherKinds
import scala.annotation.tailrec
import scala.collection.GenTraversable
import scala.collection.mutable
import scala.collection.generic.{GenericCompanion, CanBuildFrom}
object UnfoldImpl {
def unfold[CC[_], E, S](builder: mutable.Builder[E, CC[E]])(initial: S)(next: S => Option[(S, E)]): CC[E] = {
@tailrec
def build(state: S): CC[E] = {
next(state) match {
case None => builder.result()
case Some((nextState, elem)) =>
builder += elem
build(nextState)
}
}
build(initial)
}
}
现在,通过类型获取集合构建器的简单方法是什么?
我可以提出两种可能的解决方案。第一个是创建一个隐式扩展类,它扩展了GenericCompanion
- 大多数scala的内置集合的常见超类。此GenericCompanion
有一个方法newBuilder
,它为提供的元素类型返回Builder
。实现可能如下所示:
implicit class Unfolder[CC[X] <: GenTraversable[X]](obj: GenericCompanion[CC]) {
def unfold[S, E](initial: S)(next: S => Option[(S, E)]): CC[E] =
UnfoldImpl.unfold(obj.newBuilder[E])(initial)(next)
}
使用它很容易:
scala> List.unfold(1)(a => if (a > 10) None else Some(a + 1, a * a))
res1: List[Int] = List(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
一个缺点是某些集合没有扩展GenericCompanion
的伴随对象。例如,Array
或用户定义的集合。
另一种可能的解决方案是使用隐含的“构建器提供程序”,就像您提出的那样。 scala在集合库中已经有了这样的东西。这是CanBuildFrom
。 CanBuildFrom
的实现可能如下所示:
object Unfolder2 {
def apply[CC[_]] = new {
def unfold[S, E](initial: S)(next: S => Option[(S, E)])(
implicit cbf: CanBuildFrom[CC[E], E, CC[E]]
): CC[E] =
UnfoldImpl.unfold(cbf())(initial)(next)
}
}
用法示例:
scala> Unfolder2[Array].unfold(1)(a => if (a > 10) None else Some(a + 1, a * a))
res1: Array[Int] = Array(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
这适用于scala的集合Array
,如果用户提供了CanBuildFrom
实例,则可以使用用户定义的集合。
请注意,这两种方法都不能以惰性方式与Stream
一起使用。这主要是因为原始实施UnfoldImpl.unfold
使用Builder
,Stream
为eager。
要执行像Stream
懒惰展开之类的操作,您无法使用标准Builder
。您必须使用Stream.cons
(或#::
)提供单独的实施。为了能够自动选择实现,根据用户请求的集合类型,您可以使用类型类模式。以下是一个示例实现:
trait Unfolder3[E, CC[_]] {
def unfold[S](initial: S)(next: S => Option[(S, E)]): CC[E]
}
trait UnfolderCbfInstance {
// provides unfolder for types that have a `CanBuildFrom`
// this is used only if the collection is not a `Stream`
implicit def unfolderWithCBF[E, CC[_]](
implicit cbf: CanBuildFrom[CC[E], E, CC[E]]
): Unfolder3[E, CC] =
new Unfolder3[E, CC] {
def unfold[S](initial: S)(next: S => Option[(S, E)]): CC[E] =
UnfoldImpl.unfold(cbf())(initial)(next)
}
}
object Unfolder3 extends UnfolderCbfInstance {
// lazy implementation, that overrides `unfolderWithCbf` for `Stream`s
implicit def streamUnfolder[E]: Unfolder3[E, Stream] =
new Unfolder3[E, Stream] {
def unfold[S](initial: S)(next: S => Option[(S, E)]): Stream[E] =
next(initial).fold(Stream.empty[E]) {
case (state, elem) =>
elem #:: unfold(state)(next)
}
}
def apply[CC[_]] = new {
def unfold[E, S](initial: S)(next: S => Option[(S, E)])(
implicit impl: Unfolder3[E, CC]
): CC[E] = impl.unfold(initial)(next)
}
}
现在,这个实现对于正常的集合(包括Array
和用户定义的集合,包含适当的CanBuildFrom
)以及懒惰的Stream
s急切地工作:
scala> Unfolder3[Array].unfold(1)(a => if (a > 10) None else Some(a + 1, a * a))
res0: Array[Int] = Array(1, 4, 9, 16, 25, 36, 49, 64, 81, 100)
scala> com.Main.Unfolder3[Stream].unfold(1)(a => if (a > 10) None else { println(a); Some(a + 1, a * a) })
1
res2: Stream[Int] = Stream(1, ?)
scala> res2.take(3).toList
2
3
res3: List[Int] = List(1, 4, 9)
注意,如果将Unfolder3.apply
移动到另一个对象或类,则用户根本不需要导入与Unfolder3
有关的任何内容。
如果您不理解此实现的工作原理,您可以阅读有关Scala中typeclass patern和order of implicit resolution的内容。