Skip to content

Add BuildFrom #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of putting the tests into this package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to not use the empty package.


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