-
Notifications
You must be signed in to change notification settings - Fork 70
Abstractions for constrained collection types #45
Changes from 6 commits
d039e7f
f6ee4d4
9f58c61
a873a39
7c4a776
8e1fe11
82c74c2
a70f5ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package strawman.collection | ||
|
||
import scala.Any | ||
import scala.annotation.implicitNotFound | ||
import mutable.Builder | ||
|
||
/** BuildFrom can be used by methods outside the collections framework to build collections of various types without | ||
* having to define overloaded methods for unconstrained / constrained / map-like / etc. collections like we do in | ||
* the collections framework. | ||
*/ | ||
@implicitNotFound(msg = "Cannot construct a collection with elements of type ${Elem} based on a collection of type ${From}.") | ||
trait BuildFrom[-From, -Elem] { | ||
type To | ||
|
||
/** Creates a new builder on request of a collection. | ||
* @param from the collection requesting the builder to be created. | ||
* @return a builder for collections of type `To` with element type `Elem`. | ||
*/ | ||
def newBuilder(from: From): Builder[Elem, To] | ||
|
||
def fromIterable(from: From)(it: Iterable[Elem]): To | ||
} | ||
|
||
object BuildFrom { | ||
/** Build the source collection type from a PolyBuildable */ | ||
implicit def buildFromPolyBuildable[C[X] <: PolyBuildable[X, C], A, E]: BuildFrom[C[A], E] { type To = C[E] } = new BuildFrom[C[A], E] { | ||
//TODO: Reuse a prototype instance | ||
type To = C[E] | ||
def newBuilder(from: C[A]): Builder[E, To] = from.newBuilder[E] | ||
def fromIterable(from: C[A])(it: Iterable[E]): To = from.fromIterable(it) | ||
} | ||
|
||
/** Build the source collection type from a ConstrainedPolyBuildable */ | ||
implicit def buildFromConstrainedPolyBuildable[Ev[_], Impl[_], CC[_], A, E : Ev]: BuildFrom[ConstrainedPolyBuildable[A, CC, Ev], E] { type To = CC[E] } = new BuildFrom[ConstrainedPolyBuildable[A, CC, Ev], E] { | ||
//TODO: Reuse a prototype instance | ||
type To = CC[E] | ||
def newBuilder(from: ConstrainedPolyBuildable[A, CC, Ev]): Builder[E, To] = from.newConstrainedBuilder[E] | ||
def fromIterable(from: ConstrainedPolyBuildable[A, CC, Ev])(it: Iterable[E]): To = from.constrainedFromIterable(it) | ||
} | ||
|
||
/** Convert an IterableFactory to a BuildFrom */ | ||
implicit def buildIterableFactory[C[_], E](fact: IterableFactory[C]): BuildFrom[Any, E] { type To = C[E] } = new BuildFrom[Any, E] { | ||
type To = C[E] | ||
def newBuilder(from: Any): Builder[E, To] = fact.newBuilder | ||
def fromIterable(from: Any)(it: Iterable[E]): To = fact.fromIterable(it) | ||
} | ||
|
||
/** Convert a ConstrainedIterableFactory to a BuildFrom */ | ||
implicit def buildConstrainedIterableFactory[CC[_], Ev[_], E : Ev](fact: ConstrainedIterableFactory[CC, Ev]): BuildFrom[Any, E] { type To = CC[E] } = new BuildFrom[Any, E] { | ||
type To = CC[E] | ||
def newBuilder(from: Any): Builder[E, To] = fact.constrainedNewBuilder | ||
def fromIterable(from: Any)(it: Iterable[E]): To = fact.constrainedFromIterable(it) | ||
} | ||
|
||
/** Convert a MapFactory to a BuildFrom */ | ||
implicit def buildMapFactory[C[_, _], K, V](fact: MapFactory[C]): BuildFrom[Any, (K, V)] { type To = C[K, V] } = new BuildFrom[Any, (K, V)] { | ||
type To = C[K, V] | ||
def newBuilder(from: Any): Builder[(K, V), To] = fact.newBuilder | ||
def fromIterable(from: Any)(it: Iterable[(K, V)]): To = fact.fromIterable(it) | ||
} | ||
|
||
/** Convert a ConstrainedMapFactory to a BuildFrom */ | ||
implicit def buildConstrainedMapFactory[CC[_, _], Ev[_], K : Ev, V](fact: ConstrainedMapFactory[CC, Ev]): BuildFrom[Any, (K, V)] { type To = CC[K, V] } = new BuildFrom[Any, (K, V)] { | ||
type To = CC[K, V] | ||
def newBuilder(from: Any): Builder[(K, V), To] = fact.constrainedNewBuilder | ||
def fromIterable(from: Any)(it: Iterable[(K, V)]): To = fact.constrainedFromIterable(it) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ import scala.reflect.ClassTag | |
import scala.{Any, Array, Boolean, Int, StringContext, Unit} | ||
import java.lang.{String, UnsupportedOperationException} | ||
|
||
import strawman.collection.mutable.{ArrayBuffer, StringBuilder} | ||
import strawman.collection.mutable.{ArrayBuffer, Builder, StringBuilder} | ||
import java.lang.String | ||
|
||
/** Base trait for generic collections */ | ||
|
@@ -81,17 +81,22 @@ trait IterableOps[+A] extends Any { | |
/** A view representing the elements of this collection. */ | ||
def view: View[A] = View.fromIterator(iterator()) | ||
|
||
/** Given a collection factory `fi` for collections of type constructor `C`, | ||
* convert this collection to one of type `C[A]`. Example uses: | ||
/** Given a collection factory `fi`, convert this collection to the appropriate | ||
* representation for the current element type `A`. Example uses: | ||
* | ||
* xs.to(List) | ||
* xs.to(ArrayBuffer) | ||
* xs.to(BitSet) // for xs: Iterable[Int] | ||
*/ | ||
def to[C[X] <: Iterable[X]](fi: FromIterable[C]): C[A @uncheckedVariance] = | ||
// variance seems sound because `to` could just as well have been added | ||
// as a decorator. We should investigate this further to be sure. | ||
def to[F <: TypeConstrainedFromIterable[A]](fi: F): fi.To[A @uncheckedVariance] = | ||
fi.fromIterable(coll) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need the
should work, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll give it a try. (I'm used to writing type projections where this wouldn't work) |
||
|
||
// Generic version of the method above that can build anything with BuildFrom. Note that `bf` is not implicit. | ||
// We never want it to be inferred (because a collection could only rebuild itself that way) but we do rely on | ||
// the implicit conversions from the various factory types to BuildFrom. | ||
def to(bf: BuildFrom[Iterable[A], A]): bf.To = | ||
bf.fromIterable(coll)(coll) | ||
|
||
/** Convert collection to array. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure about overloading There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need both. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, actually |
||
def toArray[B >: A: ClassTag]: Array[B] = | ||
if (knownSize >= 0) copyToArray(new Array[B](knownSize), 0) | ||
|
@@ -172,7 +177,7 @@ trait IterableMonoTransforms[+A, +Repr] extends Any { | |
|
||
/** Transforms over iterables that can return collections of different element types. | ||
*/ | ||
trait IterablePolyTransforms[+A, +C[A]] extends Any { | ||
trait IterablePolyTransforms[+A, +C[_]] extends Any { | ||
protected def coll: Iterable[A] | ||
def fromIterable[B](coll: Iterable[B]): C[B] | ||
|
||
|
@@ -189,3 +194,74 @@ trait IterablePolyTransforms[+A, +C[A]] extends Any { | |
def zip[B](xs: IterableOnce[B]): C[(A @uncheckedVariance, B)] = fromIterable(View.Zip(coll, xs)) | ||
// sound bcs of VarianceNote | ||
} | ||
|
||
/** Transforms over iterables that can return collections of different element types for which an | ||
* implicit evidence is required. | ||
*/ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should explain in comment with examples why we need both |
||
trait ConstrainedIterablePolyTransforms[+A, +C[_], +CC[X] <: C[X]] extends Any with IterablePolyTransforms[A, C] { | ||
type Ev[_] | ||
|
||
protected def coll: Iterable[A] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What’s the advantage of that compared to having a self type annotation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same as in |
||
protected def constrainedFromIterable[B: Ev](it: Iterable[B]): CC[B] | ||
|
||
/** Map */ | ||
def map[B : Ev](f: A => B): CC[B] = constrainedFromIterable(View.Map(coll, f)) | ||
|
||
/** Flatmap */ | ||
def flatMap[B : Ev](f: A => IterableOnce[B]): CC[B] = constrainedFromIterable(View.FlatMap(coll, f)) | ||
|
||
/** Concatenation */ | ||
def ++[B >: A : Ev](xs: IterableOnce[B]): CC[B] = constrainedFromIterable(View.Concat(coll, xs)) | ||
|
||
/** Zip. Interesting because it requires to align to source collections. */ | ||
def zip[B](xs: IterableOnce[B])(implicit ev: Ev[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = constrainedFromIterable(View.Zip(coll, xs)) | ||
// sound bcs of VarianceNote | ||
|
||
def collect[B: Ev](pf: scala.PartialFunction[A, B]): CC[B] = flatMap(a => | ||
if (pf.isDefinedAt(a)) View.Elems(pf(a)) | ||
else View.Empty | ||
) | ||
|
||
/** Widen this collection to the most specific unconstrained collection type. */ | ||
def unconstrained: C[A @uncheckedVariance] | ||
} | ||
|
||
/** Base trait for strict collections that can be built using a builder. | ||
* @tparam A the element type of the collection | ||
* @tparam Repr the type of the underlying collection | ||
*/ | ||
trait MonoBuildable[+A, +Repr] extends Any with IterableMonoTransforms[A, Repr] { | ||
|
||
/** Creates a new builder. */ | ||
protected[this] def newBuilderWithSameElemType: Builder[A, Repr] | ||
|
||
/** Optimized, push-based version of `partition`. */ | ||
override def partition(p: A => Boolean): (Repr, Repr) = { | ||
val l, r = newBuilderWithSameElemType | ||
coll.iterator().foreach(x => (if (p(x)) l else r) += x) | ||
(l.result, r.result) | ||
} | ||
|
||
// one might also override other transforms here to avoid generating | ||
// iterators if it helps efficiency. | ||
} | ||
|
||
/** Base trait for strict collections that can be built for arbitrary element types using a builder. | ||
* @tparam A the element type of the collection | ||
* @tparam C the type constructor of the underlying collection | ||
*/ | ||
trait PolyBuildable[+A, +C[_]] extends Any with FromIterable[C] { | ||
|
||
/** Creates a new builder. */ | ||
def newBuilder[E]: Builder[E, C[E]] | ||
} | ||
|
||
/** Base trait for strict collections that can be built using a builder for element types with an implicit evidence. | ||
* @tparam A the element type of the collection | ||
* @tparam C the type constructor of the underlying collection | ||
*/ | ||
trait ConstrainedPolyBuildable[+A, +CC[_], Ev[_]] extends Any with ConstrainedFromIterable[CC, Ev] { | ||
|
||
/** Creates a new builder. */ | ||
def newConstrainedBuilder[E : Ev]: Builder[E, CC[E]] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,24 +3,46 @@ package collection | |
|
||
import strawman.collection.mutable.Builder | ||
|
||
import scala.Int | ||
import scala.{Any, Int, Nothing} | ||
import scala.annotation.unchecked.uncheckedVariance | ||
|
||
/** Base trait for instances that can construct a collection from an iterable */ | ||
trait FromIterable[+C[X] <: Iterable[X]] { | ||
def fromIterable[B](it: Iterable[B]): C[B] | ||
trait FromIterable[+C[_]] extends Any with TypeConstrainedFromIterable[Any] { | ||
type To[X] = C[X] @uncheckedVariance | ||
} | ||
|
||
/** Base trait for companion objects of collections */ | ||
trait IterableFactories[+C[X] <: Iterable[X]] extends FromIterable[C] { | ||
/** Base trait for instances that can construct a collection from an iterable for certain types | ||
* (but without needing an evidence value). */ | ||
trait TypeConstrainedFromIterable[-B] extends Any { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would say |
||
type To[_] | ||
def fromIterable[E <: B](it: Iterable[E]): To[E] | ||
} | ||
|
||
/** Base trait for instances that can construct a collection from an iterable by using an implicit evidence | ||
* for the element type. */ | ||
trait ConstrainedFromIterable[+CC[_], Ev[_]] extends Any { | ||
def constrainedFromIterable[E : Ev](it: Iterable[E]): CC[E] | ||
} | ||
|
||
/** Base trait for companion objects of unconstrained collection types */ | ||
trait IterableFactory[+C[_]] extends FromIterable[C] { self => | ||
def empty[A]: C[A] = fromIterable(View.Empty) | ||
|
||
def apply[A](xs: A*): C[A] = fromIterable(View.Elems(xs: _*)) | ||
|
||
def fill[A](n: Int)(elem: => A): C[A] = fromIterable(View.Fill(n)(elem)) | ||
|
||
def newBuilder[A]: Builder[A, C[A]] | ||
} | ||
|
||
/** Base trait for companion objects of collections that require an implicit evidence */ | ||
trait ConstrainedIterableFactory[+CC[X], Ev[_]] extends ConstrainedFromIterable[CC, Ev] { | ||
|
||
def empty[A : Ev]: CC[A] = constrainedFromIterable(View.Empty) | ||
|
||
def apply[A : Ev](xs: A*): CC[A] = constrainedFromIterable(View.Elems(xs: _*)) | ||
|
||
implicit def canBuild[A]: () => Builder[A, C[A]] = () => newBuilder[A] // TODO Reuse the same instance | ||
def fill[A : Ev](n: Int)(elem: => A): CC[A] = constrainedFromIterable(View.Fill(n)(elem)) | ||
|
||
def constrainedNewBuilder[A : Ev]: Builder[A, CC[A]] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would name it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered that, too, but it sounds like a method that creates a |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds like smell: we should not have to introduce this
Any
parameter if we don’t need it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do need for the "proper" cases (implicit values for poly builders). When you do a manual breakout, the
from
value is ignored.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, we could have a subclass like
BuildFromAny[-Elem] extends BuildFrom[Any, Elem]
with overloads that do not take afrom
value. This would be enough for the non-implicit use case into
. Is getting rid of theAny
parameter here worth the extra complexity of a new class with two new methods?