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

Introduce typeclasses making it easier to write generic extension methods #478

Merged
merged 12 commits into from
Mar 6, 2018

Conversation

julienrf
Copy link
Contributor

@julienrf julienrf commented Feb 21, 2018

Fixes #294.

array.intersperse(0)
array.view.intersperse(0)
string.intersperse(' ')
string.view.intersperse(' ')
Copy link
Contributor Author

@julienrf julienrf Feb 21, 2018

Choose a reason for hiding this comment

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

This approach works with List, Vector but also views, Array and String.

However, there seems to be some type information lost somewhere:

    val result = list.intersperse(0)
    result: List[Int]
[error]  found   : Any
[error]  required: strawman.collection.immutable.List[Int]
[error]     result: List[Int]
[error]     ^

@julienrf julienrf force-pushed the generic-decorators branch 2 times, most recently from 6dc1511 to 718bcb9 Compare February 27, 2018 09:10
@julienrf
Copy link
Contributor Author

julienrf commented Feb 27, 2018

Here is a design that make it possible to define a decorator that will work for any collection-like type, whether it is a collection (e.g. Seq[Int]), a view (e.g. SeqView[Int]) or something that can be seen as a collection (e.g. String). It is possible to program generically against this decorated type (and extract their type of elements).

  1. A decorator is an implicit class that takes an implicit HasXxxOps, where Xxx can be Set, Seq, etc.:
implicit class SeqDecorator[C, S <: HasSeqOps[C]](coll: C)(implicit val seq: S) { … }

(note that if scala/scala3#3964 or scala/bug#5712 were solved we could just write the following: implicit class SeqDecorator[C](coll: C)(implicit val seq: HasSeqOps[C]))

The definition of HasSeqOps is the following (largely inspired from the old IsSeqLike):

trait HasSeqOps[C] {
  type A
  def apply(c: C): SeqOps[A, _, _]
}

The type HasSeqOps contains a type member A referring to the type of elements of the decorated collection. For instance, if we decorate a List[Int] we would get seq.A =:= Int. Here is an example of use to set a lower bound on type a parameter taken by the extension method:

  def intersperse[B >: seq.A](sep: B) =
  1. Extension methods can use BuildFrom to compute their result type.
  def intersperse[B >: seq.A, That](sep: B)(implicit bf: BuildFrom[C, B, That]): That =
    bf.fromSpecificIterable(coll)(new View.Intersperse(seq(coll), sep))

The drawbacks of this design are:

  • decorators take two parameters, and thus can not be made value classes,
  • it is often hard to understand why an implicit HasXxxOps[C] instance has not be found for a given type C.

@julienrf julienrf changed the title [WIP] Introduce typeclasses making it easier to write generic extension methods Introduce typeclasses making it easier to write generic extension methods Feb 27, 2018
trait HasImmutableMapOps[C] extends HasMapOps[C] {

// Convenient intermediate type definitions to satisfy type bounds
protected type _CC[X, +Y] <: immutable.MapOps[X, Y, _CC, _]
Copy link
Contributor

Choose a reason for hiding this comment

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

If the user ever needs to interact with this type directly, I would change the name to something more memorable, e.g. MapOf.

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 is an implementation detail which is not supposed to be used by users. I wish I could get rid of it but couldn’t figure out how.


// Convenient intermediate type definitions to satisfy type bounds
protected type _CC[X, +Y] <: immutable.MapOps[X, Y, _CC, _]
protected type _C <: immutable.MapOps[K, V, _CC, _C]
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, if the user needs to interact with this it should have a better name, e.g. MapType.

protected type _C <: immutable.MapOps[K, V, _CC, _C]

/** A conversion from the type `C` to `immutable.MapOps[K, V, _, _]` */
val conversion: C => immutable.MapOps[K, V, _CC, _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 is this a val? I would think this would be easiest to use as a def apply, so you can (implicit ops: M) and then in the code ops(coll).

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 agree. This is fixed.

@Ichoran
Copy link
Contributor

Ichoran commented Feb 27, 2018

The strategy looks sound overall, though I do worry that the complexity will seem daunting to people even if mostly it "just works". We'll need some good documentation.

Also, I think we could improve the usability a bit (as indicated on my comments for HasImmutableMapOps, but they apply to everything).

@julienrf
Copy link
Contributor Author

In any case, this framework mainly targets advanced users.

A first support of generic decorators is already provided by BuildFrom (exactly like we used to do with CanBuildFrom in the old collections). This allows users to generically define extension methods for any collection type but views, String and Array. This should be fine for most cases.

If one needs more power (ie the ability to also abstract over views, String and Array), then HasIterableOps and friends are the right tool to use.

Last, I’ve put this machinery in the collections-contrib module. We don’t have to have it in the core.

@julienrf julienrf force-pushed the generic-decorators branch from 86b32ae to f487dc2 Compare March 1, 2018 13:14
@julienrf
Copy link
Contributor Author

julienrf commented Mar 1, 2018

Rebased

@julienrf
Copy link
Contributor Author

julienrf commented Mar 5, 2018

@szeiger any objection to merge this one?

@julienrf julienrf merged commit 56a9813 into scala:master Mar 6, 2018
@julienrf julienrf deleted the generic-decorators branch March 6, 2018 07:53
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.

2 participants