Skip to content

Commit

Permalink
Merge pull request #3 from julienrf/build-from
Browse files Browse the repository at this point in the history
Add BuildFrom
  • Loading branch information
julienrf authored Mar 28, 2018
2 parents c6327f1 + 4d424bc commit fcc3425
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 9 deletions.
38 changes: 38 additions & 0 deletions src/main/scala-2.12/collection/BuildFrom.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package scala.collection

import scala.collection.generic.CanBuildFrom

/** Builds a collection of type `C` from elements of type `A` when a source collection of type `From` is available.
* Implicit instances of `BuildFrom` are available for all collection types.
*
* @tparam From Type of source collection
* @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.)
* @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.)
*/
trait BuildFrom[-From, -A, +C] extends Any {

def fromSpecificIterable(from: From)(it: Iterable[A]): C

/** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer.
* Building collections with `fromSpecificIterable` is preferred because it can be lazy for lazy collections. */
def newBuilder(from: From): mutable.Builder[A, C]

@deprecated("Use newBuilder() instead of apply()", "2.13.0")
@`inline` def apply(from: From): mutable.Builder[A, C] = newBuilder(from)

}

object BuildFrom {

// Implicit instance derived from an implicit CanBuildFrom instance
implicit def fromCanBuildFrom[From, A, C](implicit cbf: CanBuildFrom[From, A, C]): BuildFrom[From, A, C] =
new BuildFrom[From, A, C] {
def fromSpecificIterable(from: From)(it: Iterable[A]): C = (cbf(from) ++= it).result()
def newBuilder(from: From): mutable.Builder[A, C] = cbf(from)
}

// Implicit conversion derived from an implicit conversion to CanBuildFrom
implicit def fromCanBuildFromConversion[X, From, A, C](x: X)(implicit toCanBuildFrom: X => CanBuildFrom[From, A, C]): BuildFrom[From, A, C] =
fromCanBuildFrom(toCanBuildFrom(x))

}
16 changes: 11 additions & 5 deletions src/main/scala-2.12/collection/compat/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ import scala.reflect.ClassTag
package object compat {
import scala.collection.compat_impl._

implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]](fact: GenericCompanion[CC]): CanBuildFrom[Nothing, A, CC[A]] =
implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]](fact: GenericCompanion[CC]): CanBuildFrom[Any, A, CC[A]] =
simpleCBF(fact.newBuilder[A])

implicit def arrayCompanionToCBF[A : ClassTag](fact: Array.type): CanBuildFrom[Nothing, A, Array[A]] =
implicit def sortedSetCompanionToCBF[A : Ordering, CC[X] <: SortedSet[X] with SortedSetLike[X, CC[X]]](fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] =
simpleCBF(fact.newBuilder[A])

implicit def arrayCompanionToCBF[A : ClassTag](fact: Array.type): CanBuildFrom[Any, A, Array[A]] =
simpleCBF(Array.newBuilder[A])

implicit def mapFactoryToCBF[K, V, CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](fact: MapFactory[CC]): CanBuildFrom[Nothing, (K, V), CC[K, V]] =
implicit def mapFactoryToCBF[K, V, CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]](fact: MapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] =
simpleCBF(fact.newBuilder[K, V])

implicit def sortedMapFactoryToCBF[K : Ordering, V, CC[A, B] <: SortedMap[A, B] with SortedMapLike[A, B, CC[A, B]]](fact: SortedMapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] =
simpleCBF(fact.newBuilder[K, V])

implicit def immutableBitSetFactoryToCBF(fact: BitSetFactory[immutable.BitSet]): CanBuildFrom[Nothing, Int, ImmutableBitSetCC[Int]] =
implicit def immutableBitSetFactoryToCBF(fact: BitSetFactory[immutable.BitSet]): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] =
simpleCBF(fact.newBuilder)

implicit def mutableBitSetFactoryToCBF(fact: BitSetFactory[mutable.BitSet]): CanBuildFrom[Nothing, Int, MutableBitSetCC[Int]] =
implicit def mutableBitSetFactoryToCBF(fact: BitSetFactory[mutable.BitSet]): CanBuildFrom[Any, Int, MutableBitSetCC[Int]] =
simpleCBF(fact.newBuilder)

implicit class IterableFactoryExtensionMethods[CC[X] <: GenTraversable[X]](private val fact: GenericCompanion[CC]) {
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala-2.12/collection/compat_impl/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import scala.collection.mutable.Builder
import scala.reflect.ClassTag

package object compat_impl {
def simpleCBF[A, C](f: => Builder[A, C]): CanBuildFrom[Nothing, A, C] = new CanBuildFrom[Nothing, A, C] {
def apply(from: Nothing): Builder[A, C] = apply()
def simpleCBF[A, C](f: => Builder[A, C]): CanBuildFrom[Any, A, C] = new CanBuildFrom[Any, A, C] {
def apply(from: Any): Builder[A, C] = apply()
def apply(): Builder[A, C] = f
}

Expand Down
140 changes: 140 additions & 0 deletions src/test/scala/collection/BuildFromTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package scala.collection

import org.junit.Test

import scala.collection.mutable.{ArrayBuffer, Builder, ListBuffer}
import scala.collection.immutable.{HashMap, List, TreeMap, TreeSet}
import scala.collection.compat._

// Tests copied from the 2.13 scala-library
class BuildFromTest {

// Using BuildFrom to abstract over both and also allow building arbitrary collection types
def optionSequence2[CC[X] <: Iterable[X], A, To](xs: CC[Option[A]])(implicit bf: BuildFrom[CC[Option[A]], A, To]): Option[To] =
xs.foldLeft[Option[Builder[A, To]]](Some(bf.newBuilder(xs))) {
case (Some(builder), Some(a)) => Some(builder += a)
case _ => None
}.map(_.result())

// Using dependent types:
def optionSequence3[A, To](xs: Iterable[Option[A]])(implicit bf: BuildFrom[xs.type, A, To]): Option[To] =
xs.foldLeft[Option[Builder[A, To]]](Some(bf.newBuilder(xs))) {
case (Some(builder), Some(a)) => Some(builder += a)
case _ => None
}.map(_.result())

def eitherSequence[A, B, To](xs: Iterable[Either[A, B]])(implicit bf: BuildFrom[xs.type, B, To]): Either[A, To] =
xs.foldLeft[Either[A, Builder[B, To]]](Right(bf.newBuilder(xs))) {
case (Right(builder), Right(b)) => Right(builder += b)
case (Left(a) , _) => Left(a)
case (_ , Left(a)) => Left(a)
}.right.map(_.result())

@Test
def optionSequence2Test: Unit = {
val xs1 = List(Some(1), None, Some(2))
val o1 = optionSequence2(xs1)
val o1t: Option[List[Int]] = o1

val xs2 = TreeSet(Some("foo"), Some("bar"), None)
val o2 = optionSequence2(xs2)
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
// val o2t: Option[TreeSet[String]] = o2
val o2t: Option[SortedSet[String]] = o2

// Breakout-like use case from https://github.com/scala/scala/pull/5233:
val xs4 = List[Option[(Int, String)]](Some((1 -> "a")), Some((2 -> "b")))
val o4 = optionSequence2(xs4)(TreeMap)
val o4t: Option[TreeMap[Int, String]] = o4
}

@Test
def optionSequence3Test: Unit = {
val xs1 = List(Some(1), None, Some(2))
val o1 = optionSequence3(xs1)
val o1t: Option[List[Int]] = o1

val xs2 = TreeSet(Some("foo"), Some("bar"), None)
val o2 = optionSequence3(xs2)
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
// val o2t: Option[TreeSet[String]] = o2
val o2t: Option[SortedSet[String]] = o2

// Breakout-like use case from https://github.com/scala/scala/pull/5233:
val xs4 = List[Option[(Int, String)]](Some((1 -> "a")), Some((2 -> "b")))
val o4 = optionSequence3(xs4)(TreeMap) // same syntax as in `.to`
val o4t: Option[TreeMap[Int, String]] = o4
}

@Test
def eitherSequenceTest: Unit = {
val xs3 = ListBuffer(Right("foo"), Left(0), Right("bar"))
val e1 = eitherSequence(xs3)
val e1t: Either[Int, ListBuffer[String]] = e1
}

// From https://github.com/scala/collection-strawman/issues/44
def flatCollect[A, B, To](coll: Iterable[A])(f: PartialFunction[A, IterableOnce[B]])
(implicit bf: BuildFrom[coll.type, B, To]): To = {
val builder = bf.newBuilder(coll)
for (a <- coll) {
if (f.isDefinedAt(a)) builder ++= f(a)
}
builder.result()
}

def mapSplit[A, B, C, ToL, ToR](coll: Iterable[A])(f: A => Either[B, C])
(implicit bfLeft: BuildFrom[coll.type, B, ToL], bfRight: BuildFrom[coll.type, C, ToR]): (ToL, ToR) = {
val left = bfLeft.newBuilder(coll)
val right = bfRight.newBuilder(coll)
for (a <- coll)
f(a).fold(left.+=, right.+=)
(left.result(), right.result())
}

@Test
def flatCollectTest: Unit = {
val xs1 = List(1, 2, 3)
val xs2 = flatCollect(xs1) { case 2 => ArrayBuffer("foo", "bar") }
val xs3: List[String] = xs2

val xs4 = TreeMap((1, "1"), (2, "2"))
val xs5 = flatCollect(xs4) { case (2, v) => List((v, v)) }
val xs6: TreeMap[String, String] = xs5

val xs7 = HashMap((1, "1"), (2, "2"))
val xs8 = flatCollect(xs7) { case (2, v) => List((v, v)) }
val xs9: HashMap[String, String] = xs8

val xs10 = TreeSet(1, 2, 3)
val xs11 = flatCollect(xs10) { case 2 => List("foo", "bar") }
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
// val xs12: TreeSet[String] = xs11
val xs12: SortedSet[String] = xs11
}

@Test
def mapSplitTest: Unit = {
val xs1 = List(1, 2, 3)
val (xs2, xs3) = mapSplit(xs1)(x => if (x % 2 == 0) Left(x) else Right(x.toString))
val xs4: List[Int] = xs2
val xs5: List[String] = xs3

val xs6 = TreeMap((1, "1"), (2, "2"))
val (xs7, xs8) = mapSplit(xs6) { case (k, v) => Left[(String, Int), (Int, Boolean)]((v, k)) }
val xs9: TreeMap[String, Int] = xs7
val xs10: TreeMap[Int, Boolean] = xs8
}

implicitly[BuildFrom[String, Char, String]]
implicitly[BuildFrom[Array[Int], Char, Array[Char]]]
implicitly[BuildFrom[BitSet, Int, BitSet]]
implicitly[BuildFrom[immutable.BitSet, Int, immutable.BitSet]]
implicitly[BuildFrom[mutable.BitSet, Int, mutable.BitSet]]

// Check that collection companions can implicitly be converted to a `BuildFrom` instance
Iterable: BuildFrom[_, Int, Iterable[Int]]
Map: BuildFrom[_, (Int, String), Map[Int, String]]
SortedSet: BuildFrom[_, Int, SortedSet[Int]]
SortedMap: BuildFrom[_, (Int, String), SortedMap[Int, String]]
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package collection

import java.util

import org.junit.Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import org.junit.Assert
import org.junit.Test
package collection

import org.junit.{Assert, Test}

import scala.collection.immutable.ImmutableArray

Expand Down

0 comments on commit fcc3425

Please sign in to comment.