-
Notifications
You must be signed in to change notification settings - Fork 72
Conversation
`BuildFrom` is like `FromSpecificIterable` but with an extra `From` argument, like in the final version of scala#45. `FromSpecificIterable` existed conceptually in that version as `BuildFrom[Any, …]` but didn’t have a separate type. This new version has separate abstractions for buildable (strict) collection types in the form of `StrictBuildFrom` and `FromSpecificIterableWithBuilder`. Since we can get a surrogate builder (through one of the new `Builder.from` methods) for any lazy collection and we can restrict code to work only with strict collections via the `Buildable` trait, this is not strictly necessary. The only reason for separating the `Builder` abstractions is to avoid exposing them in `FromSpecificIterable`. Even though everything can be built in a strict way, these abstractions sit on top of the lazy ones, not below them.
There is no way to get a BuildFrom purely by type in this design, so we need to call parseCollection explicitly with a companion object.
This is an extension of scala#97 which provides Builders in BuildFrom and FromSpecificIterable for all collection types because we need them for always-strict operations like groupBy and we can always get them anyway. This greatly simplifies the design of BuildFrom by getting rid of half of the factory types.
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.
On one hand, introducing the newBuilder
method in all factories is tempting because it makes factories a lot simpler! But on the other hand I’d rather not have them in View
and LazyList
factories.
In summary, we will have to decide on
- whether we want to keep or not the public builders in all the factories (including
View
andLazyList
) - in comparison with Implicit builders on top of #110 #112, do you think that would be doable to have generic tests use implicit instances?
- also in comparison with Implicit builders on top of #110 #112: in this PR we have one type of implicit builder:
BuildFrom
, which takes aFrom
parameter and which has afromSpecificIterable
method. Whereas in Implicit builders on top of #110 #112 we haveCanBuild
which has noFrom
type parameter and nofromSpecificIterable
method. Then, the idea in Implicit builders on top of #110 #112 is to deriveBuildFrom
andBuildTo
from thisCanBuild
. But if in most cases we find it useful to have theFrom
parameter maybe it makes sense to just haveBuildFrom
, as in this PR.
Do other people have thoughts?
|
||
def sortedIterableFactory = SortedSet | ||
|
||
def sortedMapFactory: SortedMapFactory[CC] | ||
|
||
protected[this] def sortedMapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)])(implicit ordering: Ordering[K2]): CC[K2, V2] |
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.
Shouldn’t this be implemented?
protected[this] def sortedMapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)])(implicit ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.sortedFromIterable(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.
Yes, I think we can delegate this methods now the same way it is already done for fromSpecificIterable
.
|
||
trait IterableFactoryWithBuilder[+CC[_]] extends IterableFactory[CC] { | ||
def newBuilder[A](): Builder[A, CC[A]] | ||
def newBuilder[A](): Builder[A, CC[A]] = new ArrayBuffer[A]().mapResult(fromIterable _) |
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.
I’m not sure this default implementation will be useful. Will it be useful elsewhere than for ArrayBuffer
and ImmutableArray
?
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 default is used for all collection types that cannot provide a better builder. This includes all lazy collections.
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.
In the case of lazy collections, what about using an ImmutableArray
instead of an ArrayBuffer
to avoid the extra space?
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.
You don't know the size ahead of time so you can either copy everything again or live with some wasted space.
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.
I mean that even after an ArrayBuffer
has been completely built there might still be some unused extra space. If you build an ImmutableArray
(or just call toArray
on the ArrayBuffer
, which is almost exactly the same thing), then you trim the end of the buffer. (But yes, that means one additional copy)
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.
Is there any precedent for this in other collection methods? AFAIK trimming to the current size is always an explicit operation. The default is to avoid copying at the expense of some unused space.
@@ -9,7 +9,7 @@ trait SortedSet[A] extends Set[A] with SortedSetOps[A, SortedSet, SortedSet[A]] | |||
|
|||
trait SortedSetOps[A, +CC[X], +C <: SortedSet[A]] | |||
extends SetOps[A, Set, C] | |||
with SortedOps[A, C] { | |||
with SortedOps[A, C, CC] { | |||
|
|||
protected[this] def sortedFromIterable[B: Ordering](it: Iterable[B]): CC[B] |
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.
It should be possible to implement this by delegating to sortedIterableFactory
.
|
||
@Test | ||
def genericTest: Unit = { | ||
assert(Parse.parseCollection[Int, immutable.List[Int]](immutable.List).parse("1|2|3").contains(1 :: 2 :: 3 :: immutable.Nil)) |
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.
It’s unfortunate that the FromSpecificIterable
can not be available implicitly. That’s a requirement to do generic typeclass derivation.
/** | ||
* Builds a collection of type `C` from elements of type `A` | ||
* @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) | ||
* @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) | ||
*/ | ||
trait FromSpecificIterable[-A, +C] extends Any { | ||
trait FromSpecificIterable[-A, +C] extends Any with BuildFrom[Any, A, C] { |
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.
Where do we rely on the fact that FromSpecificIterable
is a BuildFrom
?
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.
Whenever you pass a factory object instead of an implicit BuildFrom
. All factories provide an implicit conversion to FromSpecificIterable
.
Also, we have to figure out the issue with the dotty compilation in this PR. |
@julienrf As discussed previously we want strict methods like |
I'd like to review this but can only do it after PLDI. So it will be probably a week until you have the review. Thanks for holding that long... |
* @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 { |
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.
What does making the C
a type parameter instead of a type member do for us?
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.
I would prefer a type member (and I did use one in my original implementation in #45) but FromSpecificIterable
in Martin's simplified version uses a type parameter so I used the same here. I think we should use type members for both. Unlike CanBuildFrom
, lookup for BuildFrom
and FromSpecificIterable
is never determined by this type. It is strictly an "out" parameter.
} | ||
|
||
/** Build the source collection type from a SortedMapOps */ | ||
implicit def buildFromSortedMapOps[CC[K, V] <: SortedMap[K, V] with SortedMapOps[K, V, CC, _], A, B, E : Ordering, F]: BuildFrom[CC[A, B], (E, F), CC[E, F]] = new BuildFrom[CC[A, B], (E, F), CC[E, F]] { |
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.
Most people won't have to look at this, but it's still pretty imposing with seven (?!) separate type parameters (five "real", two parameterizing CC
).
I'm also quite comfortable with this approach. It feels a little more flexible but a little more imposing than Julien's #112. I can't easily tell what one could accomplish here that one couldn't there, but overall the type member strategy, despite requiring a little more magic, seems overall a bit friendlier. Both are pretty good, though, for what the tests cover. Do either of you have a clear idea of what the most important differences are for user-level code? I wasn't able to easily distinguish them. Also, I think there's a principle underlying builders that should simplify matters: the only reason you care about the This suggests to me that we probably don't need an explicit |
Both solutions seem to be equivalent. The only difference I noted is that in #112, we can get an implicit Also, in #112 I keep differentiating factories that have builders from those that don’t. I’d rather not expose a |
It should be possible to add an implicit method to each factory type that provides a |
One important difference is that in #112 AFAICT all Builders are constructed by expected target type, not driven by the source collection, whereas here the default implicits fall back to the source collection. This allows you to do transformations independent of the static type: The static type of the source determines the level of detail of see of the static type of the target but the implementation class is always the same. This fixes one of the consistency problem of the current collections' |
Also note that the source-driven transformation is consistent with the new way of performing operations without |
They are if you ask for an implicit But if you ask for an implicit
Indeed this feature is not supported by my PR: if the static type of the source is |
I've been playing around with type-driven building. I run into the problem that I already encountered previously that This still leaves the problem of distinguishing between the static and dynamic BuildFrom instances. With |
With regard to the actual type, not the statically visible type, being the collection: I think that's important behavior, probably important enough to favor this approach. But if it can be made to work with the simplified type parameters (pushing Why do the implicits in the factories not obey priority? Or is it just that you need to create a low priority thing for them each time which is awkward? |
For sorted maps you get a |
OK, restricting to |
This is an extension of #97 which provides Builders in BuildFrom and
FromSpecificIterable for all collection types because we need them
for always-strict operations like groupBy and we can always get them
anyway.
This greatly simplifies the design of BuildFrom by getting rid of half
of the factory types.