-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from julienrf/build-from
Add BuildFrom
- Loading branch information
Showing
6 changed files
with
196 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]] | ||
} |
2 changes: 2 additions & 0 deletions
2
src/test/scala/CollectionTest.scala → ...est/scala/collection/CollectionTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
package collection | ||
|
||
import java.util | ||
|
||
import org.junit.Test | ||
|
5 changes: 3 additions & 2 deletions
5
src/test/scala/ImmutableArrayTest.scala → ...scala/collection/ImmutableArrayTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters