具有泛型类型的泛型集合生成

时间:2016-02-28 13:15:38

标签: scala generics collections implicit

有时,我发现自己希望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;对了。

那么,有没有更好的方法来实现这一目标?

1 个答案:

答案 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在集合库中已经有了这样的东西。这是CanBuildFromCanBuildFrom的实现可能如下所示:

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使用BuilderStreameager

要执行像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 paternorder of implicit resolution的内容。