Skip to content

Commit fcc3425

Browse files
authored
Merge pull request #3 from julienrf/build-from
Add BuildFrom
2 parents c6327f1 + 4d424bc commit fcc3425

File tree

6 files changed

+196
-9
lines changed

6 files changed

+196
-9
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package scala.collection
2+
3+
import scala.collection.generic.CanBuildFrom
4+
5+
/** Builds a collection of type `C` from elements of type `A` when a source collection of type `From` is available.
6+
* Implicit instances of `BuildFrom` are available for all collection types.
7+
*
8+
* @tparam From Type of source collection
9+
* @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.)
10+
* @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.)
11+
*/
12+
trait BuildFrom[-From, -A, +C] extends Any {
13+
14+
def fromSpecificIterable(from: From)(it: Iterable[A]): C
15+
16+
/** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer.
17+
* Building collections with `fromSpecificIterable` is preferred because it can be lazy for lazy collections. */
18+
def newBuilder(from: From): mutable.Builder[A, C]
19+
20+
@deprecated("Use newBuilder() instead of apply()", "2.13.0")
21+
@`inline` def apply(from: From): mutable.Builder[A, C] = newBuilder(from)
22+
23+
}
24+
25+
object BuildFrom {
26+
27+
// Implicit instance derived from an implicit CanBuildFrom instance
28+
implicit def fromCanBuildFrom[From, A, C](implicit cbf: CanBuildFrom[From, A, C]): BuildFrom[From, A, C] =
29+
new BuildFrom[From, A, C] {
30+
def fromSpecificIterable(from: From)(it: Iterable[A]): C = (cbf(from) ++= it).result()
31+
def newBuilder(from: From): mutable.Builder[A, C] = cbf(from)
32+
}
33+
34+
// Implicit conversion derived from an implicit conversion to CanBuildFrom
35+
implicit def fromCanBuildFromConversion[X, From, A, C](x: X)(implicit toCanBuildFrom: X => CanBuildFrom[From, A, C]): BuildFrom[From, A, C] =
36+
fromCanBuildFrom(toCanBuildFrom(x))
37+
38+
}

src/main/scala-2.12/collection/compat/package.scala

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,25 @@ import scala.reflect.ClassTag
88
package object compat {
99
import scala.collection.compat_impl._
1010

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

14-
implicit def arrayCompanionToCBF[A : ClassTag](fact: Array.type): CanBuildFrom[Nothing, A, Array[A]] =
14+
implicit def sortedSetCompanionToCBF[A : Ordering, CC[X] <: SortedSet[X] with SortedSetLike[X, CC[X]]](fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] =
15+
simpleCBF(fact.newBuilder[A])
16+
17+
implicit def arrayCompanionToCBF[A : ClassTag](fact: Array.type): CanBuildFrom[Any, A, Array[A]] =
1518
simpleCBF(Array.newBuilder[A])
1619

17-
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]] =
20+
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]] =
21+
simpleCBF(fact.newBuilder[K, V])
22+
23+
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]] =
1824
simpleCBF(fact.newBuilder[K, V])
1925

20-
implicit def immutableBitSetFactoryToCBF(fact: BitSetFactory[immutable.BitSet]): CanBuildFrom[Nothing, Int, ImmutableBitSetCC[Int]] =
26+
implicit def immutableBitSetFactoryToCBF(fact: BitSetFactory[immutable.BitSet]): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] =
2127
simpleCBF(fact.newBuilder)
2228

23-
implicit def mutableBitSetFactoryToCBF(fact: BitSetFactory[mutable.BitSet]): CanBuildFrom[Nothing, Int, MutableBitSetCC[Int]] =
29+
implicit def mutableBitSetFactoryToCBF(fact: BitSetFactory[mutable.BitSet]): CanBuildFrom[Any, Int, MutableBitSetCC[Int]] =
2430
simpleCBF(fact.newBuilder)
2531

2632
implicit class IterableFactoryExtensionMethods[CC[X] <: GenTraversable[X]](private val fact: GenericCompanion[CC]) {

src/main/scala-2.12/collection/compat_impl/package.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import scala.collection.mutable.Builder
55
import scala.reflect.ClassTag
66

77
package object compat_impl {
8-
def simpleCBF[A, C](f: => Builder[A, C]): CanBuildFrom[Nothing, A, C] = new CanBuildFrom[Nothing, A, C] {
9-
def apply(from: Nothing): Builder[A, C] = apply()
8+
def simpleCBF[A, C](f: => Builder[A, C]): CanBuildFrom[Any, A, C] = new CanBuildFrom[Any, A, C] {
9+
def apply(from: Any): Builder[A, C] = apply()
1010
def apply(): Builder[A, C] = f
1111
}
1212

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package scala.collection
2+
3+
import org.junit.Test
4+
5+
import scala.collection.mutable.{ArrayBuffer, Builder, ListBuffer}
6+
import scala.collection.immutable.{HashMap, List, TreeMap, TreeSet}
7+
import scala.collection.compat._
8+
9+
// Tests copied from the 2.13 scala-library
10+
class BuildFromTest {
11+
12+
// Using BuildFrom to abstract over both and also allow building arbitrary collection types
13+
def optionSequence2[CC[X] <: Iterable[X], A, To](xs: CC[Option[A]])(implicit bf: BuildFrom[CC[Option[A]], A, To]): Option[To] =
14+
xs.foldLeft[Option[Builder[A, To]]](Some(bf.newBuilder(xs))) {
15+
case (Some(builder), Some(a)) => Some(builder += a)
16+
case _ => None
17+
}.map(_.result())
18+
19+
// Using dependent types:
20+
def optionSequence3[A, To](xs: Iterable[Option[A]])(implicit bf: BuildFrom[xs.type, A, To]): Option[To] =
21+
xs.foldLeft[Option[Builder[A, To]]](Some(bf.newBuilder(xs))) {
22+
case (Some(builder), Some(a)) => Some(builder += a)
23+
case _ => None
24+
}.map(_.result())
25+
26+
def eitherSequence[A, B, To](xs: Iterable[Either[A, B]])(implicit bf: BuildFrom[xs.type, B, To]): Either[A, To] =
27+
xs.foldLeft[Either[A, Builder[B, To]]](Right(bf.newBuilder(xs))) {
28+
case (Right(builder), Right(b)) => Right(builder += b)
29+
case (Left(a) , _) => Left(a)
30+
case (_ , Left(a)) => Left(a)
31+
}.right.map(_.result())
32+
33+
@Test
34+
def optionSequence2Test: Unit = {
35+
val xs1 = List(Some(1), None, Some(2))
36+
val o1 = optionSequence2(xs1)
37+
val o1t: Option[List[Int]] = o1
38+
39+
val xs2 = TreeSet(Some("foo"), Some("bar"), None)
40+
val o2 = optionSequence2(xs2)
41+
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
42+
// val o2t: Option[TreeSet[String]] = o2
43+
val o2t: Option[SortedSet[String]] = o2
44+
45+
// Breakout-like use case from https://github.com/scala/scala/pull/5233:
46+
val xs4 = List[Option[(Int, String)]](Some((1 -> "a")), Some((2 -> "b")))
47+
val o4 = optionSequence2(xs4)(TreeMap)
48+
val o4t: Option[TreeMap[Int, String]] = o4
49+
}
50+
51+
@Test
52+
def optionSequence3Test: Unit = {
53+
val xs1 = List(Some(1), None, Some(2))
54+
val o1 = optionSequence3(xs1)
55+
val o1t: Option[List[Int]] = o1
56+
57+
val xs2 = TreeSet(Some("foo"), Some("bar"), None)
58+
val o2 = optionSequence3(xs2)
59+
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
60+
// val o2t: Option[TreeSet[String]] = o2
61+
val o2t: Option[SortedSet[String]] = o2
62+
63+
// Breakout-like use case from https://github.com/scala/scala/pull/5233:
64+
val xs4 = List[Option[(Int, String)]](Some((1 -> "a")), Some((2 -> "b")))
65+
val o4 = optionSequence3(xs4)(TreeMap) // same syntax as in `.to`
66+
val o4t: Option[TreeMap[Int, String]] = o4
67+
}
68+
69+
@Test
70+
def eitherSequenceTest: Unit = {
71+
val xs3 = ListBuffer(Right("foo"), Left(0), Right("bar"))
72+
val e1 = eitherSequence(xs3)
73+
val e1t: Either[Int, ListBuffer[String]] = e1
74+
}
75+
76+
// From https://github.com/scala/collection-strawman/issues/44
77+
def flatCollect[A, B, To](coll: Iterable[A])(f: PartialFunction[A, IterableOnce[B]])
78+
(implicit bf: BuildFrom[coll.type, B, To]): To = {
79+
val builder = bf.newBuilder(coll)
80+
for (a <- coll) {
81+
if (f.isDefinedAt(a)) builder ++= f(a)
82+
}
83+
builder.result()
84+
}
85+
86+
def mapSplit[A, B, C, ToL, ToR](coll: Iterable[A])(f: A => Either[B, C])
87+
(implicit bfLeft: BuildFrom[coll.type, B, ToL], bfRight: BuildFrom[coll.type, C, ToR]): (ToL, ToR) = {
88+
val left = bfLeft.newBuilder(coll)
89+
val right = bfRight.newBuilder(coll)
90+
for (a <- coll)
91+
f(a).fold(left.+=, right.+=)
92+
(left.result(), right.result())
93+
}
94+
95+
@Test
96+
def flatCollectTest: Unit = {
97+
val xs1 = List(1, 2, 3)
98+
val xs2 = flatCollect(xs1) { case 2 => ArrayBuffer("foo", "bar") }
99+
val xs3: List[String] = xs2
100+
101+
val xs4 = TreeMap((1, "1"), (2, "2"))
102+
val xs5 = flatCollect(xs4) { case (2, v) => List((v, v)) }
103+
val xs6: TreeMap[String, String] = xs5
104+
105+
val xs7 = HashMap((1, "1"), (2, "2"))
106+
val xs8 = flatCollect(xs7) { case (2, v) => List((v, v)) }
107+
val xs9: HashMap[String, String] = xs8
108+
109+
val xs10 = TreeSet(1, 2, 3)
110+
val xs11 = flatCollect(xs10) { case 2 => List("foo", "bar") }
111+
// Not working: the resolved implicit BuildFrom results in a SortedSet instead of a TreeSet
112+
// val xs12: TreeSet[String] = xs11
113+
val xs12: SortedSet[String] = xs11
114+
}
115+
116+
@Test
117+
def mapSplitTest: Unit = {
118+
val xs1 = List(1, 2, 3)
119+
val (xs2, xs3) = mapSplit(xs1)(x => if (x % 2 == 0) Left(x) else Right(x.toString))
120+
val xs4: List[Int] = xs2
121+
val xs5: List[String] = xs3
122+
123+
val xs6 = TreeMap((1, "1"), (2, "2"))
124+
val (xs7, xs8) = mapSplit(xs6) { case (k, v) => Left[(String, Int), (Int, Boolean)]((v, k)) }
125+
val xs9: TreeMap[String, Int] = xs7
126+
val xs10: TreeMap[Int, Boolean] = xs8
127+
}
128+
129+
implicitly[BuildFrom[String, Char, String]]
130+
implicitly[BuildFrom[Array[Int], Char, Array[Char]]]
131+
implicitly[BuildFrom[BitSet, Int, BitSet]]
132+
implicitly[BuildFrom[immutable.BitSet, Int, immutable.BitSet]]
133+
implicitly[BuildFrom[mutable.BitSet, Int, mutable.BitSet]]
134+
135+
// Check that collection companions can implicitly be converted to a `BuildFrom` instance
136+
Iterable: BuildFrom[_, Int, Iterable[Int]]
137+
Map: BuildFrom[_, (Int, String), Map[Int, String]]
138+
SortedSet: BuildFrom[_, Int, SortedSet[Int]]
139+
SortedMap: BuildFrom[_, (Int, String), SortedMap[Int, String]]
140+
}

src/test/scala/CollectionTest.scala renamed to src/test/scala/collection/CollectionTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
package collection
2+
13
import java.util
24

35
import org.junit.Test

src/test/scala/ImmutableArrayTest.scala renamed to src/test/scala/collection/ImmutableArrayTest.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import org.junit.Assert
2-
import org.junit.Test
1+
package collection
2+
3+
import org.junit.{Assert, Test}
34

45
import scala.collection.immutable.ImmutableArray
56

0 commit comments

Comments
 (0)