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

Make the different kinds of collection companions consistent together #36

Merged
merged 3 commits into from
Feb 27, 2017

Conversation

julienrf
Copy link
Contributor

@julienrf julienrf commented Feb 24, 2017

Also, define an implicit builder factory () => Builder[A, C[A]] that can be used by end-users to retrieve a factory for a given collection type. For instance we can now write Future.sequence (or equivalent) as follows:

def futureSequence[A, C[X] <: Iterable[X]](xs: C[Future[A]])(implicit canBuild: () => Builder[A, C[A]]): Future[C[A]] =
  xs.foldLeft(Future.successful(canBuild.apply())) { (fr, fa) =>
    fr.zipWith(fa)(_ += _)
  }.map(_.result)

Note that I duplicated the collection factories: IterableFactory works only for collection types of kind * -> *, OrderingGuidedFactory works for the same kind of collection types but requires an implicit Ordering, MapFactory works for collection types of kind * -> * -> *, and SortedMapFactory is the same but requires an implicit Ordering for keys. I don’t think this duplication is a problem. Trying to abstract over these difference would not be worth it, in my opinion.

…. Define an implicit CanBuild that can be used by end-users to retrieve a factory for a given collection type.
}

/** Base trait for builder factories */
trait CanBuild[-A, +Repr] {
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 gain anything from this beyond just having the type be () => Builder[A, C[A]]?

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 find a little bit nicer to have implicit vals of type CanBuild[…] rather than () => Builder[…]. I find it also a little bit nicer to take parameters of type CanBuild[…] rather than () => Builder[…]. This is a matter of taste…

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, but in the former case we'd need only
implicit def canBuild[A] = () => newBuilder[A, C[A]] instead of the four lines we have now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I was reluctant to define implicit vals with type Function1 because they can act as implicit conversions, but in the case the type () => _ the corresponding type is Function0 so it’s perfectly fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I’m thinking that CanBuild also has the following advantages:

  • Simpler to use for end-users (e.g. the type of the parameter is CanBuild[A, C[A]] instead of () => Builder[A, C[A]]) ;
  • We could add other methods than just apply(): Builder[A, C[A]], like apply(knownSize: Int): Builder[A, C[A]].

@julienrf julienrf merged commit b03e0e3 into master Feb 27, 2017
@julienrf julienrf deleted the companion-builders branch February 27, 2017 10:35
@szeiger
Copy link
Contributor

szeiger commented Feb 28, 2017

I'm not convinced by this CanBuild abstraction. It adds a bias towards strict builders and yet another concept. We already have companion objects for collections, so why don't we make them first-class? Every companion object could provide itself as an implicit value:

object List extends CollectionCompanion[List] {
  implicit val companion: CollectionCompanion[List] = this
}

This provides convenient access to both, FromIterable and strict builders, as well as other potential features that may be mixed into the companion object.

The limitation to unary type constructors isn't particularly nice. This would be better (if we don't run into any variance problems; I haven't tried it):

object List extends CollectionCompanion[List[Any]] {
  implicit def companion[T]: CollectionCompanion[List[T]] = this
}

companion would not be part of any trait, so collections are free to define it in whatever way is appropriate, e.g.:

object HashMap extends CollectionCompanion[HashMap[Any, Nothing]] {
  implicit def companion[K, V]: CollectionCompanion[HashMap[K, V]] =
    this // may require cast or @uncheckedVariance
}

@szeiger
Copy link
Contributor

szeiger commented Feb 28, 2017

Small addition: It doesn't fully cover constrained collections that require more than a type evidence. We can safely cast if variance is an issue but for Sorted, for example, we need the actual Ordered value, not just a proof that it exists.

@szeiger
Copy link
Contributor

szeiger commented Feb 28, 2017

Continuing this line of thought, constrained collections cannot implement IterableFactory either, but they could provide an IterableFactory for a specific element type, thus providing the same interface as unconstrained collection types after an implicit lookup. I'll think some more about this and try to apply it to my universal constrained collections PR.

@szeiger
Copy link
Contributor

szeiger commented Mar 1, 2017

Something to keep in mind: scala/scala#5233

Rather than "allow me to build whatever I want" the essence of this seems to be "make it work for Maps". It would be nice to get this right by default, which requires moving away from any design that requires a unary type constructor. (The "allow me to build whatever" case can be covered by Views)

}

/** Base trait for companion objects of collections */
trait IterableFactories[+C[X] <: Iterable[X]] extends FromIterable[C] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this renamed from IterableFactory to IterableFactories?

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 thought it would make more sense because this trait provides several factory methods. We can just settle on either XxxFactory or XxxFactories

@odersky
Copy link
Contributor

odersky commented Mar 7, 2017 via email

retronym added a commit to retronym/collection-strawman that referenced this pull request Mar 27, 2018
```
⚡ scala
Welcome to Scala 2.12.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_162).
Type in expressions for evaluation. Or try :help.

scala> class OfDim { Array.ofDim[Any](32) }
defined class OfDim

scala> :javap -c OfDim
Compiled from "<console>"
public class $line3.$read$$iw$$iw$OfDim {
  public $line3.$read$$iw$$iw$OfDim();
    Code:
       0: aload_0
       1: invokespecial scala#17                 // Method java/lang/Object."<init>":()V
       4: getstatic     scala#23                 // Field scala/Array$.MODULE$:Lscala/Array$;
       7: bipush        32
       9: getstatic     scala#28                 // Field scala/reflect/ClassTag$.MODULE$:Lscala/reflect/ClassTag$;
      12: invokevirtual scala#32                 // Method scala/reflect/ClassTag$.Any:()Lscala/reflect/ClassTag;
      15: invokevirtual scala#36                 // Method scala/Array$.ofDim:(ILscala/reflect/ClassTag;)Ljava/lang/Object;
      18: pop
      19: return
}

scala> class NewArray { new Array[Object](32) }
defined class NewArray

scala> :javap -c NewArray
Compiled from "<console>"
public class $line4.$read$$iw$$iw$NewArray {
  public $line4.$read$$iw$$iw$NewArray();
    Code:
       0: aload_0
       1: invokespecial scala#17                 // Method java/lang/Object."<init>":()V
       4: bipush        32
       6: anewarray     scala#4                  // class java/lang/Object
       9: pop
      10: return
}
```
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