Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.

Implicit builders on top of #110 #112

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 1 addition & 1 deletion src/main/scala/strawman/collection/ArrayOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ArrayOps[A](val xs: Array[A])
protected[this] def fromTaggedIterable[B: ClassTag](coll: Iterable[B]): Array[B] = coll.toArray[B]
protected[this] def fromSpecificIterable(coll: Iterable[A]): Array[A] = coll.toArray[A](elemTag)

protected[this] def newSpecificBuilder() = new GrowableBuilder(ArrayBuffer.empty[A]).mapResult(_.toArray(elemTag))
protected[this] def newSpecificBuilder() = ArrayBuffer.newBuilder[A]().mapResult(_.toArray(elemTag))
Copy link
Contributor

Choose a reason for hiding this comment

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

This could avoid copying if the size matches. We may also consider passing a size hint to newSpecificBuilder (but it's probably not as useful in the new design where most operations use fromSpecificIterable instead).


override def knownSize = xs.length

Expand Down
88 changes: 88 additions & 0 deletions src/main/scala/strawman/collection/BuildFrom.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package strawman.collection

import strawman.collection.mutable.Builder

import scala.{Any, Ordering}

/**
* Auxiliary type allowing us to define which collection type to build according to the type of an existing collection.
*
* @tparam From Existing collection type that will drives the collection type to build
* @tparam A Element type
*/
trait BuildFrom[-From, -A] {

/** Collection type to build */
type To

def newBuilder(): Builder[A, To]

}

trait BuildFromLowPriority {

/**
* Extracts the binary type constructor of `From` and applies it to `A` and `B`
* to build the `To` type.
*/
implicit def binaryTC[CC[_, _], A, B](implicit
cb: CanBuild[(A, B), CC[A, B]]
): BuildFrom.Aux[CC[_, _], (A, B), CC[A, B]] =
new BuildFrom[CC[_, _], (A, B)] {
type To = CC[A, B]
def newBuilder(): Builder[(A, B), CC[A, B]] = cb.newBuilder()
}

}

object BuildFrom extends BuildFromLowPriority {

type Aux[From, A, To0] = BuildFrom[From, A] { type To = To0 }
Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't collide with Shapeless' name here. The type signature is consistent with something called some variant on BuildSpecification. That's obviously too long, but Aux is too uninformative (in addition to the slight chance of confusion from shadowing).

Copy link
Contributor

Choose a reason for hiding this comment

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

This type makes things really confusing. Can we get rid of it and use either type members or type parameters everywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I disagree. According to the context it can be more convenient to use type members or type parameters. That’s why this pattern is so prevalent in shapeless.

I can find a better name than Aux, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree. I would try to use parameters everywhere because that's how we started. Also, if we bring back CanBuildFrom in a way, it's best not to do arbitrary changes to it.

Copy link
Contributor

Choose a reason for hiding this comment

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

The whole thing looks still very complex to me. Let's see whether we can simplify by going to all parameters.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think Build[From, A, To] ends up seeming too complex even though it is "all parameters". If the Aux-style transformation of a member into a parameter is something that can be behind the scenes in almost every case, I think that would give a better user experience. If people get to interact with BuildFrom[From, A] instead of a triply parameterized thing, I think they'll be happier. Also, the triply parameterized one is kind of misleading because we don't fill in most of Build[From, A, To] in an interesting way for specific choices of From and To. Generally if we know what we want to go To we don't care where we came from; and if we know where we come From there is only a single choice of where to go To.


/**
* Extracts the unary type constructor of `From` and applies it to `A`
* to build the `To` type.
*/
implicit def unaryTC[CC[_], A](implicit
cb: CanBuild[A, CC[A]]
): BuildFrom.Aux[CC[_], A, CC[A]] =
new BuildFrom[CC[_], A] {
type To = CC[A]
def newBuilder(): Builder[A, CC[A]] = cb.newBuilder()
}

// Explicit BuildFrom instances allowing breakOut-like style (see the tests for usage examples)
// We have to define four variants to support all the combinations of factories (unary vs binary type
// constructor, ordered vs unordered)

def factory[CC[_], A](iterableFactory: IterableFactoryWithBuilder[CC]): BuildFrom.Aux[Any, A, CC[A]] =
new BuildFrom[Any, A] {
type To = CC[A]
def newBuilder(): Builder[A, CC[A]] = iterableFactory.newBuilder[A]()
}

def factory[CC[_], A](sortedIterableFactory: SortedIterableFactoryWithBuilder[CC])(implicit ordering: Ordering[A]): BuildFrom.Aux[Any, A, CC[A]] =
new BuildFrom[Any, A] {
type To = CC[A]
def newBuilder(): Builder[A, CC[A]] = sortedIterableFactory.newBuilder[A]()
}

def factory[CC[X, Y] <: Map[X, Y] with MapOps[X, Y, CC, _], K, V](
mapFactory: MapFactoryWithBuilder[CC]
): BuildFrom.Aux[Any, (K, V), CC[K, V]] =
new BuildFrom[Any, (K, V)] {
type To = CC[K, V]
def newBuilder(): Builder[(K, V), CC[K, V]] = mapFactory.newBuilder[K, V]()
}

def factory[CC[X, Y] <: SortedMap[X, Y] with SortedMapOps[X, Y, CC, _], K, V](
sortedMapFactory: SortedMapFactoryWithBuilder[CC]
)(implicit
ordering: Ordering[K]
): BuildFrom.Aux[Any, (K, V), CC[K, V]] =
new BuildFrom[Any, (K, V)] {
type To = CC[K, V]
def newBuilder(): Builder[(K, V), CC[K, V]] = sortedMapFactory.newBuilder[K, V]()
}

}
30 changes: 30 additions & 0 deletions src/main/scala/strawman/collection/BuildTo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package strawman.collection

import strawman.collection.mutable.Builder

/** Auxiliary data type used to retrieve the element type `Elem` of a complete collection type `C` */
trait BuildTo[C] {

type Elem

def newBuilder(): Builder[Elem, C]

}

object BuildTo {

/** Provides a `BuildTo` based on an available `CanBuild` for a unary collection type constructor */
implicit def unaryTC[CC[_], A](implicit cb: CanBuild[A, CC[A]]): BuildTo[CC[A]] { type Elem = A } =
new BuildTo[CC[A]] {
type Elem = A
def newBuilder(): Builder[A, CC[A]] = cb.newBuilder()
}

/** Provides a `BuildTo` based on an available `CanBuild` for a binary collection type constructor */
implicit def binaryTC[CC[_, _], A, B](implicit cb: CanBuild[(A, B), CC[A, B]]): BuildTo[CC[A, B]] { type Elem = (A, B) } =
new BuildTo[CC[A, B]] {
type Elem = (A, B)
def newBuilder(): Builder[(A, B), CC[A, B]] = cb.newBuilder()
}

}
19 changes: 19 additions & 0 deletions src/main/scala/strawman/collection/Factories.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ trait FromSpecificIterable[-A, +C] extends Any {
def fromSpecificIterable(it: Iterable[A]): C
}

/**
* Builder factory
* @tparam A Element type (e.g. `Int`)
* @tparam C Collection type (e.g. `List[Int]`)
*/
// Note that the `C` parameter is fundamentally covariant. However if we tell that
// to the compiler, then the implicit search does not work anymore (it works with
// dotty, though). Hence the absence of variance annotation here, and the presence
// of the `@uncheckedVariance` annotations below
trait CanBuild[-A, /*+*/C] {
/** Creates a builder */
def newBuilder(): Builder[A, C]
}

/** Base trait for companion objects of unconstrained collection types */
trait IterableFactory[+CC[_]] {

Expand Down Expand Up @@ -45,6 +59,7 @@ object IterableFactory {

trait IterableFactoryWithBuilder[+CC[_]] extends IterableFactory[CC] {
def newBuilder[A](): Builder[A, CC[A]]
implicit def canBuild[A]: CanBuild[A, CC[A] @uncheckedVariance] = () => newBuilder[A]()
}

object IterableFactoryWithBuilder {
Expand All @@ -65,6 +80,7 @@ trait SpecificIterableFactory[-A, +C] extends FromSpecificIterable[A, C] {

trait SpecificIterableFactoryWithBuilder[-A, +C] extends SpecificIterableFactory[A, C] {
def newBuilder(): Builder[A, C]
implicit def canBuild: CanBuild[A, C @uncheckedVariance] = () => newBuilder()
}

/** Factory methods for collections of kind `* −> * -> *` */
Expand Down Expand Up @@ -93,6 +109,7 @@ object MapFactory {

trait MapFactoryWithBuilder[+CC[_, _]] extends MapFactory[CC] {
def newBuilder[K, V](): Builder[(K, V), CC[K, V]]
implicit def canBuild[K, V]: CanBuild[(K, V), CC[K, V] @uncheckedVariance] = () => newBuilder[K, V]()
}

object MapFactoryWithBuilder {
Expand Down Expand Up @@ -134,6 +151,7 @@ object SortedIterableFactory {

trait SortedIterableFactoryWithBuilder[+CC[_]] extends SortedIterableFactory[CC] {
def newBuilder[A : Ordering](): Builder[A, CC[A]]
implicit def canBuild[A : Ordering]: CanBuild[A, CC[A] @uncheckedVariance] = () => newBuilder[A]()
}

object SortedIterableFactoryWithBuilder {
Expand Down Expand Up @@ -172,6 +190,7 @@ object SortedMapFactory {

trait SortedMapFactoryWithBuilder[+CC[_, _]] extends SortedMapFactory[CC] {
def newBuilder[K : Ordering, V](): Builder[(K, V), CC[K, V]]
implicit def canBuild[K : Ordering, V]: CanBuild[(K, V), CC[K, V] @uncheckedVariance] = () => newBuilder[K, V]()
}

object SortedMapFactoryWithBuilder {
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/strawman/collection/immutable/BitSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ object BitSet extends SpecificIterableFactoryWithBuilder[Int, BitSet] {
def empty: BitSet = new BitSet1(0L)

def newBuilder(): Builder[Int, BitSet] =
new GrowableBuilder(mutable.BitSet.empty).mapResult(bs => fromBitMaskNoCopy(bs.elems))
mutable.BitSet.newBuilder().mapResult(bs => fromBitMaskNoCopy(bs.elems))

@SerialVersionUID(2260107458435649300L)
class BitSet1(val elems: Long) extends BitSet {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ object ImmutableArray extends IterableFactoryWithBuilder[ImmutableArray] {
new ImmutableArray(ArrayBuffer.fromIterable(it).asInstanceOf[ArrayBuffer[Any]].toArray)

def newBuilder[A](): Builder[A, ImmutableArray[A]] =
new GrowableBuilder(ArrayBuffer.empty[A])
ArrayBuffer.newBuilder[A]()
.mapResult(b => new ImmutableArray[A](b.asInstanceOf[ArrayBuffer[Any]].toArray))

override def fill[A](n: Int)(elem: => A): ImmutableArray[A] = tabulate(n)(_ => elem)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/strawman/collection/immutable/List.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object List extends IterableFactoryWithBuilder[List] {
case _ => ListBuffer.fromIterable(coll).toList
}

def newBuilder[A](): Builder[A, List[A]] = new GrowableBuilder(ListBuffer.empty[A]).mapResult(_.toList)
def newBuilder[A](): Builder[A, List[A]] = ListBuffer.newBuilder[A]().mapResult(_.toList)

def empty[A]: List[A] = Nil
}
6 changes: 4 additions & 2 deletions src/main/scala/strawman/collection/mutable/ArrayBuffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int)

protected[this] def fromSpecificIterable(coll: collection.Iterable[A]): ArrayBuffer[A] = fromIterable(coll)

protected[this] def newSpecificBuilder() = new GrowableBuilder(ArrayBuffer.empty[A])
protected[this] def newSpecificBuilder() = ArrayBuffer.newBuilder[A]()

def clear(): Unit =
end = 0
Expand Down Expand Up @@ -121,7 +121,7 @@ class ArrayBuffer[A] private (initElems: Array[AnyRef], initLength: Int)
override def className = "ArrayBuffer"
}

object ArrayBuffer extends IterableFactory[ArrayBuffer] {
object ArrayBuffer extends IterableFactoryWithBuilder[ArrayBuffer] {

/** Avoid reallocation of buffer if length is known. */
def fromIterable[B](coll: collection.Iterable[B]): ArrayBuffer[B] =
Expand All @@ -134,6 +134,8 @@ object ArrayBuffer extends IterableFactory[ArrayBuffer] {
else new ArrayBuffer[B] ++= coll

def empty[A]: ArrayBuffer[A] = new ArrayBuffer[A]()

def newBuilder[A](): Builder[A, ArrayBuffer[A]] = new GrowableBuilder(empty[A])
}

class ArrayBufferView[A](val array: Array[AnyRef], val length: Int) extends IndexedView[A] {
Expand Down
5 changes: 3 additions & 2 deletions src/main/scala/strawman/collection/mutable/BitSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class BitSet(protected[collection] final var elems: Array[Long])
protected[this] def fromSpecificIterable(coll: collection.Iterable[Int]): BitSet =
BitSet.fromSpecificIterable(coll)

protected[this] def newSpecificBuilder(): Builder[Int, BitSet] = new GrowableBuilder(BitSet.empty)
protected[this] def newSpecificBuilder(): Builder[Int, BitSet] = BitSet.newBuilder()

protected[collection] final def nwords: Int = elems.length

Expand Down Expand Up @@ -101,10 +101,11 @@ class BitSet(protected[collection] final var elems: Array[Long])

}

object BitSet extends SpecificIterableFactory[Int, BitSet] {
object BitSet extends SpecificIterableFactoryWithBuilder[Int, BitSet] {

def fromSpecificIterable(it: strawman.collection.Iterable[Int]): BitSet = Growable.fromIterable(empty, it)

def empty: BitSet = new BitSet()

def newBuilder(): Builder[Int, BitSet] = new GrowableBuilder(empty)
}
5 changes: 3 additions & 2 deletions src/main/scala/strawman/collection/mutable/HashMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class HashMap[K, V] private[collection] (contents: HashTable.Contents[K, D
protected[this] def fromSpecificIterable(coll: collection.Iterable[(K, V)]): HashMap[K, V] = HashMap.fromIterable(coll)
protected[this] def mapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)]): HashMap[K2, V2] = HashMap.fromIterable(it)

protected[this] def newSpecificBuilder(): Builder[(K, V), HashMap[K, V]] = new GrowableBuilder(HashMap.empty[K, V])
protected[this] def newSpecificBuilder(): Builder[(K, V), HashMap[K, V]] = HashMap.newBuilder[K, V]()

def iterator(): Iterator[(K, V)] = table.entriesIterator.map(e => (e.key, e.value))

Expand Down Expand Up @@ -93,12 +93,13 @@ final class HashMap[K, V] private[collection] (contents: HashTable.Contents[K, D

}

object HashMap extends MapFactory[HashMap] {
object HashMap extends MapFactoryWithBuilder[HashMap] {

def empty[K, V]: HashMap[K, V] = new HashMap[K, V]

def fromIterable[K, V](it: collection.Iterable[(K, V)]): HashMap[K, V] = Growable.fromIterable(empty[K, V], it)

def newBuilder[K, V](): Builder[(K, V), HashMap[K, V]] = new GrowableBuilder(empty[K, V])
}

/** Class used internally for default map model.
Expand Down
5 changes: 3 additions & 2 deletions src/main/scala/strawman/collection/mutable/HashSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ final class HashSet[A](contents: FlatHashTable.Contents[A])

protected[this] def fromSpecificIterable(coll: collection.Iterable[A]): HashSet[A] = fromIterable(coll)

protected[this] def newSpecificBuilder(): Builder[A, HashSet[A]] = new GrowableBuilder(HashSet.empty[A])
protected[this] def newSpecificBuilder(): Builder[A, HashSet[A]] = HashSet.newBuilder[A]()

def add(elem: A): this.type = {
table.addElem(elem)
Expand Down Expand Up @@ -75,10 +75,11 @@ final class HashSet[A](contents: FlatHashTable.Contents[A])

}

object HashSet extends IterableFactory[HashSet] {
object HashSet extends IterableFactoryWithBuilder[HashSet] {

def fromIterable[B](it: strawman.collection.Iterable[B]): HashSet[B] = Growable.fromIterable(empty[B], it)

def empty[A]: HashSet[A] = new HashSet[A]

def newBuilder[A](): Builder[A, HashSet[A]] = new GrowableBuilder(empty[A])
}
6 changes: 4 additions & 2 deletions src/main/scala/strawman/collection/mutable/ListBuffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ListBuffer[A]
def length = len
override def knownSize = len

protected[this] def newSpecificBuilder() = new GrowableBuilder(ListBuffer.empty[A])
protected[this] def newSpecificBuilder() = ListBuffer.newBuilder[A]()

private def copyElems(): Unit = {
val buf = ListBuffer.fromIterable(this)
Expand Down Expand Up @@ -197,9 +197,11 @@ class ListBuffer[A]
override def className = "ListBuffer"
}

object ListBuffer extends IterableFactory[ListBuffer] {
object ListBuffer extends IterableFactoryWithBuilder[ListBuffer] {

def fromIterable[A](coll: collection.Iterable[A]): ListBuffer[A] = new ListBuffer[A] ++= coll

def empty[A]: ListBuffer[A] = new ListBuffer[A]

def newBuilder[A](): Builder[A, ListBuffer[A]] = new GrowableBuilder(empty[A])
}
5 changes: 3 additions & 2 deletions src/main/scala/strawman/collection/mutable/TreeMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ sealed class TreeMap[K, V] private (tree: RB.Tree[K, V])(implicit val ordering:

protected[this] def sortedMapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)])(implicit ordering: Ordering[K2]): TreeMap[K2, V2] = TreeMap.sortedFromIterable(it)

protected[this] def newSpecificBuilder(): Builder[(K, V), TreeMap[K, V]] = new GrowableBuilder(TreeMap.empty[K, V])
protected[this] def newSpecificBuilder(): Builder[(K, V), TreeMap[K, V]] = TreeMap.newBuilder[K, V]()

def iterator(): Iterator[(K, V)] = RB.iterator(tree)

Expand Down Expand Up @@ -175,11 +175,12 @@ sealed class TreeMap[K, V] private (tree: RB.Tree[K, V])(implicit val ordering:
* @define Coll mutable.TreeMap
* @define coll mutable tree map
*/
object TreeMap extends SortedMapFactory[TreeMap] {
object TreeMap extends SortedMapFactoryWithBuilder[TreeMap] {

def sortedFromIterable[K : Ordering, V](it: collection.Iterable[(K, V)]): TreeMap[K, V] =
Growable.fromIterable(empty[K, V], it)

def empty[K : Ordering, V]: TreeMap[K, V] = new TreeMap[K, V]()

def newBuilder[K: Ordering, V](): Builder[(K, V), TreeMap[K, V]] = new GrowableBuilder(empty[K, V])
}
5 changes: 3 additions & 2 deletions src/main/scala/strawman/collection/mutable/TreeSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ sealed class TreeSet[A] private (tree: RB.Tree[A, Null])(implicit val ordering:

protected[this] def fromSpecificIterable(coll: collection.Iterable[A]): TreeSet[A] = TreeSet.sortedFromIterable(coll)

protected[this] def newSpecificBuilder(): Builder[A, TreeSet[A]] = new GrowableBuilder(TreeSet.empty[A])
protected[this] def newSpecificBuilder(): Builder[A, TreeSet[A]] = TreeSet.newBuilder[A]()

def iterableFactory = Set

Expand Down Expand Up @@ -178,10 +178,11 @@ sealed class TreeSet[A] private (tree: RB.Tree[A, Null])(implicit val ordering:
* @author Lucien Pereira
*
*/
object TreeSet extends SortedIterableFactory[TreeSet] {
object TreeSet extends SortedIterableFactoryWithBuilder[TreeSet] {

def empty[A : Ordering]: TreeSet[A] = new TreeSet[A]()

def sortedFromIterable[E : Ordering](it: collection.Iterable[E]): TreeSet[E] = Growable.fromIterable(empty[E], it)

def newBuilder[A: Ordering](): Builder[A, TreeSet[A]] = new GrowableBuilder(empty[A])
}
Loading