Skip to content

Commit ac08696

Browse files
authored
Introduce typeclasses making it easier to write generic extension methods (#478)
* Introduce HasSeqOps * Introduce HasImmutableMapOps * Introduce HasIterableOps
1 parent 8cb2bbe commit ac08696

13 files changed

+313
-72
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package strawman.collection
2+
package decorators
3+
4+
/**
5+
* Type class witnessing that a collection type `C` has
6+
* a conversion to `immutable.MapOps[K, V, _, _]`.
7+
*
8+
* @see [[scala.collection.decorators.HasIterableOps]]
9+
*/
10+
trait HasImmutableMapOps[C] extends HasMapOps[C] {
11+
12+
// Convenient intermediate type definitions to satisfy type bounds
13+
protected type _CC[X, +Y] <: immutable.MapOps[X, Y, _CC, _]
14+
protected type _C <: immutable.MapOps[K, V, _CC, _C]
15+
16+
/** A conversion from the type `C` to `immutable.MapOps[K, V, _, _]` */
17+
def apply(c: C): immutable.MapOps[K, V, _CC, _C]
18+
19+
}
20+
21+
object HasImmutableMapOps {
22+
23+
// 1. Map collections
24+
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 } =
25+
new HasImmutableMapOps[CC[K0, V0]] {
26+
type K = K0
27+
type V = V0
28+
type _CC[X, +Y] = CC[X, Y]
29+
type _C = CC[K, V]
30+
def apply(c: CC[K0, V0]): immutable.MapOps[K0, V0, _CC, _C] = c
31+
}
32+
33+
// 2. Sorted Map collections
34+
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 } =
35+
new HasImmutableMapOps[CC[K0, V0]] {
36+
type K = K0
37+
type V = V0
38+
type _CC[X, +Y] = immutable.Map[X, Y]
39+
type _C = _CC[K, V]
40+
def apply(c: CC[K0, V0]): immutable.MapOps[K0, V0, _CC, _C] = c
41+
}
42+
43+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package strawman.collection
2+
package decorators
3+
4+
/**
5+
* Type class witnessing that a collection type `C`
6+
* has elements of type `A` and has a conversion to `IterableOps[A, _, _]`.
7+
*
8+
* This type enables simple enrichment of `Iterable`s with extension methods.
9+
*
10+
* @tparam C Collection type (e.g. `List[Int]`)
11+
*/
12+
trait HasIterableOps[C] {
13+
14+
/** The type of elements (e.g. `Int`) */
15+
type A
16+
17+
/** A conversion from the type `C` to `IterableOps[A, _, _]` */
18+
def apply(c: C): IterableOps[A, AnyConstr, _]
19+
20+
}
21+
22+
object HasIterableOps extends LowPriorityHasIterableOps {
23+
24+
implicit def iterableHasIterableOps[CC[X] <: IterableOps[X, AnyConstr, _], A0]: HasIterableOps[CC[A0]] { type A = A0 } =
25+
new HasIterableOps[CC[A0]] {
26+
type A = A0
27+
def apply(c: CC[A0]): IterableOps[A0, AnyConstr, _] = c
28+
}
29+
30+
}
31+
32+
trait LowPriorityHasIterableOps {
33+
34+
// Makes `HasSeqOps` instances visible in `HasIterableOps` companion
35+
implicit def hasSeqOpsHasIterableOps[C, A0](implicit
36+
hasSeqOps: HasSeqOps[C] { type A = A0 }
37+
): HasIterableOps[C] { type A = A0 } = hasSeqOps
38+
39+
// Makes `HasMapOps` instances visible in `HasIterableOps` companion
40+
implicit def hasMapOpsHasIterableOps[C, K0, V0](implicit
41+
hasMapOps: HasMapOps[C] { type K = K0; type V = V0 }
42+
): HasIterableOps[C] { type A = (K0, V0) } = hasMapOps
43+
44+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package strawman.collection
2+
package decorators
3+
4+
/**
5+
* Type class witnessing that a collection type `C`
6+
* has keys of type `K`, values of type `V` and has a conversion to `MapOps[K, V, _, _]`.
7+
*
8+
* This type enables simple enrichment of `Map`s with extension methods.
9+
*
10+
* @tparam C Collection type (e.g. `Map[Int, String]`)
11+
*/
12+
trait HasMapOps[C] extends HasIterableOps[C] {
13+
14+
/** The type of keys */
15+
type K
16+
17+
/** The type of values */
18+
type V
19+
20+
type A = (K, V)
21+
22+
/** A conversion from the type `C` to `MapOps[K, V, _, _]` */
23+
def apply(c: C): MapOps[K, V, ({ type l[X, +Y] = IterableOps[_, AnyConstr, _] })#l, _]
24+
25+
}
26+
27+
object HasMapOps extends LowPriorityHasMapOps {
28+
29+
// 1. Map collections
30+
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 } =
31+
new HasMapOps[CC[K0, V0]] {
32+
type K = K0
33+
type V = V0
34+
def apply(c: CC[K0, V0]): MapOps[K0, V0, ({ type l[X, +Y] = IterableOps[_, AnyConstr, _] })#l, _] = c
35+
}
36+
37+
}
38+
39+
trait LowPriorityHasMapOps {
40+
41+
// Makes `HasImmutableMapOps` instances visible in `HasMapOps` companion
42+
implicit def hasImmutableMapOpsHasMapOps[C, K0, V0](implicit
43+
hasImmutableMapOps: HasImmutableMapOps[C] { type K = K0; type V = V0 }
44+
): HasMapOps[C] { type K = K0; type V = V0 } = hasImmutableMapOps
45+
46+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package strawman.collection
2+
package decorators
3+
4+
import scala.{Array, Char, Int}
5+
import scala.Predef.String
6+
import strawman.collection.immutable.{ImmutableArray, Range}
7+
8+
/** Type class witnessing that a collection type `C` has
9+
* elements of type `A` and has a conversion to `SeqOps[A, _, _]`.
10+
*
11+
* This type enables simple enrichment of `Seq`s with extension methods which
12+
* can make full use of the mechanics of the Scala collections framework in
13+
* their implementation.
14+
*
15+
* @see [[scala.collection.decorators.HasIterableOps]]
16+
*/
17+
trait HasSeqOps[C] extends HasIterableOps[C] {
18+
/** A conversion from the type `C` to `SeqOps[A, _, _]`. */
19+
def apply(c: C): SeqOps[A, AnyConstr, _]
20+
}
21+
22+
object HasSeqOps {
23+
24+
// we want to provide implicit instances that unify all possible types `X` with a `SeqOps[A, CC, C]`
25+
// 1. Seq collections
26+
implicit def seqHasSeqOps[CC[X] <: SeqOps[X, AnyConstr, _], A0]: HasSeqOps[CC[A0]] {type A = A0 } =
27+
new HasSeqOps[CC[A0]] {
28+
type A = A0
29+
def apply(c: CC[A0]): SeqOps[A0, AnyConstr, _] = c
30+
}
31+
32+
// 2. String
33+
implicit def stringHasSeqOps: HasSeqOps[String] { type A = Char } =
34+
new HasSeqOps[String] {
35+
type A = Char
36+
def apply(c: String): SeqOps[Char, AnyConstr, _] = stringToStringOps(c)
37+
}
38+
39+
// 3. StringView
40+
implicit def stringViewHasSeqOps: HasSeqOps[StringView] { type A = Char } =
41+
new HasSeqOps[StringView] {
42+
type A = Char
43+
def apply(c: StringView): SeqOps[Char, AnyConstr, _] = c
44+
}
45+
46+
// 4. Array
47+
implicit def arrayHasSeqOps[A0]: HasSeqOps[Array[A0]] { type A = A0 } =
48+
new HasSeqOps[Array[A0]] {
49+
type A = A0
50+
def apply(c: Array[A0]): SeqOps[A0, AnyConstr, _] = ImmutableArray.unsafeWrapArray(c)
51+
}
52+
53+
// 5. Range collections
54+
implicit def rangeHasSeqOps[C <: Range]: HasSeqOps[C] { type A = Int } =
55+
new HasSeqOps[C] {
56+
type A = Int
57+
def apply(c: C): SeqOps[Int, AnyConstr, _] = c
58+
}
59+
60+
}

src/main/scala/strawman/collection/decorators/ImmutableMapDecorator.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package strawman
22
package collection
33
package decorators
44

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

77
/**
88
* Updates an existing binding or create a new one according to the
@@ -23,15 +23,16 @@ class ImmutableMapDecorator[K, V, CC[X, +Y] <: immutable.Map[X, Y]](`this`: CC[K
2323
*
2424
* @return A new updated `Map`
2525
*/
26-
def updatedWith[C](key: K)(f: PartialFunction[Option[V], Option[V]])(implicit bf: BuildFrom[CC[K, V], (K, V), C]): C = {
26+
def updatedWith[That](key: map.K)(f: PartialFunction[Option[map.V], Option[map.V]])(implicit bf: BuildFrom[C, (map.K, map.V), That]): That = {
2727
val pf = f.lift
28+
val `this` = map(coll)
2829
val previousValue = `this`.get(key)
2930
pf(previousValue) match {
30-
case None => bf.fromSpecificIterable(`this`)(`this`)
31+
case None => bf.fromSpecificIterable(coll)(`this`.toIterable)
3132
case Some(result) =>
3233
result match {
33-
case None => bf.fromSpecificIterable(`this`)(`this` - key)
34-
case Some(v) => bf.fromSpecificIterable(`this`)(`this` + (key -> v))
34+
case None => bf.fromSpecificIterable(coll)((`this` - key).toIterable)
35+
case Some(v) => bf.fromSpecificIterable(coll)((`this` + (key -> v)).toIterable)
3536
}
3637
}
3738
}

src/main/scala/strawman/collection/decorators/IterableDecorator.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package strawman
22
package collection
33
package decorators
44

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

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

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

3334
}

0 commit comments

Comments
 (0)