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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package strawman.collection
package decorators

/**
* Type class witnessing that a collection type `C` has
* a conversion to `immutable.MapOps[K, V, _, _]`.
*
* @see [[scala.collection.decorators.HasIterableOps]]
*/
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.

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.


/** A conversion from the type `C` to `immutable.MapOps[K, V, _, _]` */
def apply(c: C): immutable.MapOps[K, V, _CC, _C]

}

object HasImmutableMapOps {

// 1. Map collections
implicit def mapHasMapOps[CC[X, +Y] <: immutable.MapOps[X, Y, CC, CC[X, Y]], K0, V0]: HasImmutableMapOps[CC[K0, V0]] { type K = K0; type V = V0 } =
new HasImmutableMapOps[CC[K0, V0]] {
type K = K0
type V = V0
type _CC[X, +Y] = CC[X, Y]
type _C = CC[K, V]
def apply(c: CC[K0, V0]): immutable.MapOps[K0, V0, _CC, _C] = c
}

// 2. Sorted Map collections
implicit def sortedMapHasMapOps[CC[X, +Y] <: immutable.Map[X, Y] with immutable.SortedMapOps[X, Y, CC, CC[X, Y]], K0, V0]: HasImmutableMapOps[CC[K0, V0]] { type K = K0; type V = V0 } =
new HasImmutableMapOps[CC[K0, V0]] {
type K = K0
type V = V0
type _CC[X, +Y] = immutable.Map[X, Y]
type _C = _CC[K, V]
def apply(c: CC[K0, V0]): immutable.MapOps[K0, V0, _CC, _C] = c
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package strawman.collection
package decorators

/**
* Type class witnessing that a collection type `C`
* has elements of type `A` and has a conversion to `IterableOps[A, _, _]`.
*
* This type enables simple enrichment of `Iterable`s with extension methods.
*
* @tparam C Collection type (e.g. `List[Int]`)
*/
trait HasIterableOps[C] {

/** The type of elements (e.g. `Int`) */
type A

/** A conversion from the type `C` to `IterableOps[A, _, _]` */
def apply(c: C): IterableOps[A, AnyConstr, _]

}

object HasIterableOps extends LowPriorityHasIterableOps {

implicit def iterableHasIterableOps[CC[X] <: IterableOps[X, AnyConstr, _], A0]: HasIterableOps[CC[A0]] { type A = A0 } =
new HasIterableOps[CC[A0]] {
type A = A0
def apply(c: CC[A0]): IterableOps[A0, AnyConstr, _] = c
}

}

trait LowPriorityHasIterableOps {

// Makes `HasSeqOps` instances visible in `HasIterableOps` companion
implicit def hasSeqOpsHasIterableOps[C, A0](implicit
hasSeqOps: HasSeqOps[C] { type A = A0 }
): HasIterableOps[C] { type A = A0 } = hasSeqOps

// Makes `HasMapOps` instances visible in `HasIterableOps` companion
implicit def hasMapOpsHasIterableOps[C, K0, V0](implicit
hasMapOps: HasMapOps[C] { type K = K0; type V = V0 }
): HasIterableOps[C] { type A = (K0, V0) } = hasMapOps

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package strawman.collection
package decorators

/**
* Type class witnessing that a collection type `C`
* has keys of type `K`, values of type `V` and has a conversion to `MapOps[K, V, _, _]`.
*
* This type enables simple enrichment of `Map`s with extension methods.
*
* @tparam C Collection type (e.g. `Map[Int, String]`)
*/
trait HasMapOps[C] extends HasIterableOps[C] {

/** The type of keys */
type K

/** The type of values */
type V

type A = (K, V)

/** A conversion from the type `C` to `MapOps[K, V, _, _]` */
def apply(c: C): MapOps[K, V, ({ type l[X, +Y] = IterableOps[_, AnyConstr, _] })#l, _]

}

object HasMapOps extends LowPriorityHasMapOps {

// 1. Map collections
implicit def mapHasMapOps[CC[X, +Y] <: MapOps[X, Y, ({ type l[X, +Y] = IterableOps[_, AnyConstr, _] })#l, _], K0, V0]: HasMapOps[CC[K0, V0]] { type K = K0; type V = V0 } =
new HasMapOps[CC[K0, V0]] {
type K = K0
type V = V0
def apply(c: CC[K0, V0]): MapOps[K0, V0, ({ type l[X, +Y] = IterableOps[_, AnyConstr, _] })#l, _] = c
}

}

trait LowPriorityHasMapOps {

// Makes `HasImmutableMapOps` instances visible in `HasMapOps` companion
implicit def hasImmutableMapOpsHasMapOps[C, K0, V0](implicit
hasImmutableMapOps: HasImmutableMapOps[C] { type K = K0; type V = V0 }
): HasMapOps[C] { type K = K0; type V = V0 } = hasImmutableMapOps

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package strawman.collection
package decorators

import scala.{Array, Char, Int}
import scala.Predef.String
import strawman.collection.immutable.{ImmutableArray, Range}

/** Type class witnessing that a collection type `C` has
* elements of type `A` and has a conversion to `SeqOps[A, _, _]`.
*
* This type enables simple enrichment of `Seq`s with extension methods which
* can make full use of the mechanics of the Scala collections framework in
* their implementation.
*
* @see [[scala.collection.decorators.HasIterableOps]]
*/
trait HasSeqOps[C] extends HasIterableOps[C] {
/** A conversion from the type `C` to `SeqOps[A, _, _]`. */
def apply(c: C): SeqOps[A, AnyConstr, _]
}

object HasSeqOps {

// we want to provide implicit instances that unify all possible types `X` with a `SeqOps[A, CC, C]`
// 1. Seq collections
implicit def seqHasSeqOps[CC[X] <: SeqOps[X, AnyConstr, _], A0]: HasSeqOps[CC[A0]] {type A = A0 } =
new HasSeqOps[CC[A0]] {
type A = A0
def apply(c: CC[A0]): SeqOps[A0, AnyConstr, _] = c
}

// 2. String
implicit def stringHasSeqOps: HasSeqOps[String] { type A = Char } =
new HasSeqOps[String] {
type A = Char
def apply(c: String): SeqOps[Char, AnyConstr, _] = stringToStringOps(c)
}

// 3. StringView
implicit def stringViewHasSeqOps: HasSeqOps[StringView] { type A = Char } =
new HasSeqOps[StringView] {
type A = Char
def apply(c: StringView): SeqOps[Char, AnyConstr, _] = c
}

// 4. Array
implicit def arrayHasSeqOps[A0]: HasSeqOps[Array[A0]] { type A = A0 } =
new HasSeqOps[Array[A0]] {
type A = A0
def apply(c: Array[A0]): SeqOps[A0, AnyConstr, _] = ImmutableArray.unsafeWrapArray(c)
}

// 5. Range collections
implicit def rangeHasSeqOps[C <: Range]: HasSeqOps[C] { type A = Int } =
new HasSeqOps[C] {
type A = Int
def apply(c: C): SeqOps[Int, AnyConstr, _] = c
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package strawman
package collection
package decorators

class ImmutableMapDecorator[K, V, CC[X, +Y] <: immutable.Map[X, Y]](`this`: CC[K, V]) {
class ImmutableMapDecorator[C, M <: HasImmutableMapOps[C]](coll: C)(implicit val map: M) {

/**
* Updates an existing binding or create a new one according to the
Expand All @@ -23,15 +23,16 @@ class ImmutableMapDecorator[K, V, CC[X, +Y] <: immutable.Map[X, Y]](`this`: CC[K
*
* @return A new updated `Map`
*/
def updatedWith[C](key: K)(f: PartialFunction[Option[V], Option[V]])(implicit bf: BuildFrom[CC[K, V], (K, V), C]): C = {
def updatedWith[That](key: map.K)(f: PartialFunction[Option[map.V], Option[map.V]])(implicit bf: BuildFrom[C, (map.K, map.V), That]): That = {
val pf = f.lift
val `this` = map(coll)
val previousValue = `this`.get(key)
pf(previousValue) match {
case None => bf.fromSpecificIterable(`this`)(`this`)
case None => bf.fromSpecificIterable(coll)(`this`.toIterable)
case Some(result) =>
result match {
case None => bf.fromSpecificIterable(`this`)(`this` - key)
case Some(v) => bf.fromSpecificIterable(`this`)(`this` + (key -> v))
case None => bf.fromSpecificIterable(coll)((`this` - key).toIterable)
case Some(v) => bf.fromSpecificIterable(coll)((`this` + (key -> v)).toIterable)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package strawman
package collection
package decorators

class IterableDecorator[A](val `this`: Iterable[A]) extends AnyVal {
class IterableDecorator[C, I <: HasIterableOps[C]](coll: C)(implicit val it: I) {

/**
* Left to right fold that stops if the combination function `op`
Expand All @@ -14,8 +14,8 @@ class IterableDecorator[A](val `this`: Iterable[A]) extends AnyVal {
* going left to right with the start value `z` on the left, and stopping when
* all the elements have been traversed or earlier if the operator returns `None`
*/
def foldSomeLeft[B](z: B)(op: (B, A) => Option[B]): B =
`this`.iterator().foldSomeLeft(z)(op)
def foldSomeLeft[B](z: B)(op: (B, it.A) => Option[B]): B =
it(coll).iterator().foldSomeLeft(z)(op)

/**
* Right to left fold that can be interrupted before traversing the whole collection.
Expand All @@ -28,6 +28,7 @@ class IterableDecorator[A](val `this`: Iterable[A]) extends AnyVal {
* then `result` is returned without iterating further; if it returns `Right(f)`, the function
* `f` is applied to the previous result to produce the new result and the fold continues.
*/
def lazyFoldRight[B](z: B)(op: A => Either[B, B => B]): B = `this`.iterator().lazyFoldRight(z)(op)
def lazyFoldRight[B](z: B)(op: it.A => Either[B, B => B]): B =
it(coll).iterator().lazyFoldRight(z)(op)

}
Loading