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

Retrofit BuildFrom into the new design #97

Closed
wants to merge 3 commits into from

Conversation

szeiger
Copy link
Contributor

@szeiger szeiger commented Jun 2, 2017

Extension of #85, work in progress

@julienrf julienrf changed the base branch from master to equals June 4, 2017 06:14
@julienrf julienrf changed the base branch from equals to master June 4, 2017 06:14
@julienrf julienrf mentioned this pull request Jun 12, 2017
`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.
@szeiger szeiger force-pushed the expose-factories-cb branch from 4efa362 to 720b52d Compare June 12, 2017 14:18
@szeiger szeiger changed the title New CanBuildFrom abstraction Retrofit BuildFrom into the new design Jun 12, 2017
@szeiger
Copy link
Contributor Author

szeiger commented Jun 12, 2017

BuildFrom is like FromSpecificIterable but with an extra From
argument, like in the final version of #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.

@szeiger
Copy link
Contributor Author

szeiger commented Jun 12, 2017

@odersky @julienrf I just updated this with the finished version of the design we discussed at ScalaDays.

}.map(_.result)

// ...or use 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] =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should work for the tests below if you ask for a StrictBuildFrom instead of a BuildFrom but it doesn't because Set is not Buildable at the moment. I'm currently working on changing that but I ran into some complications, so I wanted to get this PR out first. Making Sets buildable is a separate concern.


implicit def ordering: Ordering[A]

def sortedIterableFactory: SortedIterableFactory[CC]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This PR also adds the another factor kinds. So far every collection exposes an IterableFactory. Now in addition to that every sorted collection has a SortedIterableFactory. We'll probably want to add MapFactory and SortedMapFactory types to the appropriate collection types as well.

Copy link
Contributor

@julienrf julienrf left a comment

Choose a reason for hiding this comment

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

Thanks for working on this!

I left a few comments. Could you check that you are able to implement all the tests that I implemented in #91?

Also, I really feel like this PR introduces a lot of complexity in factories. I wonder if we could find a better compromise between #91 (which only introduces strict factories and implicit builder factories) and this PR.


object BuildFrom {
/** Build the source collection type from an IterableOps */
implicit def buildFromIterableOps[C[X] <: IterableOps[X, C, _], A, E]: BuildFrom[C[A], E, C[E]] = new BuildFrom[C[A], E, C[E]] {
Copy link
Contributor

Choose a reason for hiding this comment

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

The naming convention is to use CC for Collection Constructors

/** A more specific `FromSpecificIterable` for strict collection types which can provide a `Builder`. */
trait FromSpecificIterableWithBuilder[-A, +C] extends Any with FromSpecificIterable[A, C] with StrictBuildFrom[Any, A, C] {
def newBuilder(from: Any): Builder[A, C] = newBuilder
def newBuilder: Builder[A, C]
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer that we add parens to emphasize the fact that each created builder has its own identity: def newBuilder(): Builder[A, C].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have many methods without a parameter list that can return objects with different identities, for example Iterable.tail or IterableFactory.empty. The name newBuilder in particular makes it clear that you get a new object. I would prefer to omit empty parameter lists wherever possible.

*
* All factories provided by such a collection must be of the `*WithBuilder` variant
* even though this is not formally enforced through types.
*
Copy link
Contributor

Choose a reason for hiding this comment

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

This is unfortunate that we can not enforce this constraint :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can relax it from must to should though. Some strict collections may not be able to provide a more efficient builder anyway so it would be OK to fall back to a an ArrayBuffer. Implementing a WithBuilder factory would be an optimization, just like overriding a default implementation of some collection operation.

@@ -3,10 +3,12 @@ package strawman.collection
import scala.{Ordering, Option, Some}

/** Base trait for sorted collections */
trait SortedOps[A, +C] {
trait SortedOps[A, +C, +CC[_]] {
Copy link
Contributor

Choose a reason for hiding this comment

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

So far we usually order the type parameters such that CC is before C.

with SortedOps[K, C] {
with SortedOps[K, C, SortedSet] {

def sortedIterableFactory = SortedSet
Copy link
Contributor

Choose a reason for hiding this comment

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

Does that mean that when I navigate through the sortedIterableFactory of a SortedMap then I get back a SortedSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. This is a side-effect of SortedOps being shared by sorted sets and maps. I was also baffled by this at first but it does make sense. A sorted map is sorted by key. If you assume a standard Ordering on tuples, it degrades nicely to a sorted set. The ordering for (K,V) with unique K values is the same and with K already being unique, so is (K,V).

@@ -113,4 +115,5 @@ object TreeSet extends SortedIterableFactory[TreeSet] {
case _ => empty[E] ++ it
}

def newBuilder[A : Ordering](): Builder[A, TreeSet[A]] = new ArrayBuffer[A].mapResult(sortedFromIterable[A] _)
Copy link
Contributor

Choose a reason for hiding this comment

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

What’s the advantage of using an intermediate ArrayBuffer over directly building a TreeSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, right, the current collections append directly to the immutable TreeSet. We should do the same.

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'll address those issues in the Set PR. Doing it the old way requires a SetBuilder, which in turn requires a + operation on Sets that we do not have yet...

@@ -27,6 +27,7 @@ sealed abstract class BitSet
def empty: BitSet = BitSet.empty

def iterableFactory = Set
def sortedIterableFactory = SortedSet
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can’t we tell that the factory of a BitSet collection is the BitSet object?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the SortedIterableFactory can build a collection for every element type with an Ordering.


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 ArrayBuffer[A].mapResult(sortedFromIterable[A] _)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not directly grow a TreeSet?

@odersky
Copy link
Contributor

odersky commented Jun 13, 2017

Hi Stefan, can you push this the strawman repo? Makes it easier for me to check it out locally and play with it.

@szeiger
Copy link
Contributor Author

szeiger commented Jun 13, 2017

@odersky Done. I can confirm that this PR is the cause of the problem. Master compiles fine on the current Dotty nightly, this PR fails.

szeiger added 2 commits June 14, 2017 17:29
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.
@szeiger
Copy link
Contributor Author

szeiger commented Jun 14, 2017

I've added the missing map factory support and ported some of the additional tests from #91 (which use Map and SortedMap types). Next up: Collapsing strict building and lazy building again (like in #45), which should remove a lot of the boilerplate.

def fromSpecificIterable(it: Iterable[A]): C
}

/** A more specific `FromSpecificIterable` for strict collection types which can provide a `Builder`. */
trait FromSpecificIterableWithBuilder[-A, +C] extends Any with FromSpecificIterable[A, C] with StrictBuildFrom[Any, A, C] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need this type? It does not seem to be consumed anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not per so but we do need FromSpecificIterable with StrictBuildFrom

def parseCollection[A, C](bf: BuildFrom[Any, A, C])
(implicit parseA: Parse[A]): Parse[C] = { (s: String) =>
val parts = s.split("\\|")
parts.foldLeft[Option[Builder[A, C]]](Some(Builder.from(bf, null))) { (maybeBuilder, s) =>
Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that the first parameter of BuildFrom is Any and the second parameter of Build.from is null gives me the feeling that BuildFrom is not really the appropriate abstraction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FromSpecificIterable should be sufficient. AFAIR I ran into some problem with the implicits with that.

szeiger added a commit to szeiger/collection-strawman that referenced this pull request Jun 16, 2017
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.
@julienrf julienrf closed this Jun 27, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants