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

Conversation

julienrf
Copy link
Contributor

@julienrf julienrf commented Jun 15, 2017

I rebased #91 on top of #110.

The goal of this PR is to be easily comparable to #97, which solves the same problems (see their tests) in a different way.

The first commit shows the essence of the approach: it introduces a CanBuild trait and add implicit instances of it in all strict factories. The remaining of the commit makes mutable collection factories strict again and then adds several tests.

The second commit moves the BuildFrom and BuildTo abstractions from the tests to the main project (they could live in a different project, if needed…). The key idea is to show that CanBuild (introduced in the first commit) is enough to derive useful other abstractions such as BuildFrom and BuildTo (these ones could live in their own project, they have no impact on the core).

@julienrf julienrf requested review from szeiger, odersky and Ichoran June 15, 2017 09:40

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.

import scala.math.Ordering
import java.lang.String

class BuildFromTest {
Copy link
Contributor

Choose a reason for hiding this comment

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

I really like the usage here. There's a bit of confusing magic about "how does it even know the collection type I mean when I didn't really say what it was?"

But the magic works and it feels way more usable to me than CBF did. Especially since xs is now just a plain Iterable[Option[A]] instead of some generified parameterized thingy that might be hard to get right.

Copy link
Contributor Author

@julienrf julienrf Jun 19, 2017

Choose a reason for hiding this comment

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

Note that this style is not required, these examples would work as well as follows:

// def optionSequence[A](xs: Iterable[Option[A]])(implicit bf: BuildFrom[xs.type, A]): Option[bf.To]
def optionSequence[A, CC[X] <: Iterable[X]](xs: CC[Option[A]])(implicit bf: BuildFrom[CC[Option[A]], A]): Option[bf.To]

Also, users can choose to use a path dependent type (bf.To) or to take an additional To type parameter (by using BuildFrom.Aux -- which I could rename to something else).

Copy link
Contributor

Choose a reason for hiding this comment

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

And you should be able to do the same with the current CanBuildFrom. It certainly works with the new BuildFrom. I have examples of both styles in my PR.

@Ichoran
Copy link
Contributor

Ichoran commented Jun 17, 2017

I haven't looked at Stefan's latest version yet, but I'm quite content with the usage of this version. It's got one bit of surprising magic (that the type lookups are driven off of xs.type) that might be a bit hard to wrap one's head around as a new user and/or might be hard to debug, but once you get the pattern it's really straightforward to use.

@julienrf julienrf changed the base branch from iterable-builders to master June 21, 2017 13:33
@@ -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).

@odersky
Copy link
Contributor

odersky commented Jun 27, 2017

Overall this has the advantage that it is relatively short. But I find it confusing that we have so many different types concerned with Builders.

@julienrf julienrf closed this Jun 27, 2017
@julienrf julienrf deleted the expose-factories-and-provide-canbuild-bis branch June 29, 2017 11:29
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.

4 participants