|
| 1 | +/* |
| 2 | + * Scala (https://www.scala-lang.org) |
| 3 | + * |
| 4 | + * Copyright EPFL and Lightbend, Inc. |
| 5 | + * |
| 6 | + * Licensed under Apache License 2.0 |
| 7 | + * (http://www.apache.org/licenses/LICENSE-2.0). |
| 8 | + * |
| 9 | + * See the NOTICE file distributed with this work for |
| 10 | + * additional information regarding copyright ownership. |
| 11 | + */ |
| 12 | + |
| 13 | +package scala.collection |
| 14 | +package generic |
| 15 | +import language.experimental.captureChecking |
| 16 | + |
| 17 | +/** A trait which can be used to avoid code duplication when defining extension |
| 18 | + * methods that should be applicable both to existing Scala collections (i.e., |
| 19 | + * types extending `Iterable`) as well as other (potentially user-defined) |
| 20 | + * types that could be converted to a Scala collection type. This trait |
| 21 | + * makes it possible to treat Scala collections and types that can be implicitly |
| 22 | + * converted to a collection type uniformly. For example, one can provide |
| 23 | + * extension methods that work both on collection types and on `String`s (`String`s |
| 24 | + * do not extend `Iterable`, but can be converted to `Iterable`) |
| 25 | + * |
| 26 | + * `IsIterable` provides three members: |
| 27 | + * |
| 28 | + * 1. type member `A`, which represents the element type of the target `Iterable[A]` |
| 29 | + * 1. type member `C`, which represents the type returned by transformation operations that preserve the collection’s elements type |
| 30 | + * 1. method `apply`, which provides a way to convert between the type we wish to add extension methods to, `Repr`, and `IterableOps[A, Iterable, C]`. |
| 31 | + * |
| 32 | + * ===Usage=== |
| 33 | + * |
| 34 | + * One must provide `IsIterable` as an implicit parameter type of an implicit |
| 35 | + * conversion. Its usage is shown below. Our objective in the following example |
| 36 | + * is to provide a generic extension method `mapReduce` to any type that extends |
| 37 | + * or can be converted to `Iterable`. In our example, this includes |
| 38 | + * `String`. |
| 39 | + * |
| 40 | + * {{{ |
| 41 | + * import scala.collection.{Iterable, IterableOps} |
| 42 | + * import scala.collection.generic.IsIterable |
| 43 | + * |
| 44 | + * class ExtensionMethods[Repr, I <: IsIterable[Repr]](coll: Repr, it: I) { |
| 45 | + * def mapReduce[B](mapper: it.A => B)(reducer: (B, B) => B): B = { |
| 46 | + * val iter = it(coll).iterator |
| 47 | + * var res = mapper(iter.next()) |
| 48 | + * while (iter.hasNext) |
| 49 | + * res = reducer(res, mapper(iter.next())) |
| 50 | + * res |
| 51 | + * } |
| 52 | + * } |
| 53 | + * |
| 54 | + * implicit def withExtensions[Repr](coll: Repr)(implicit it: IsIterable[Repr]): ExtensionMethods[Repr, it.type] = |
| 55 | + * new ExtensionMethods(coll, it) |
| 56 | + * |
| 57 | + * // See it in action! |
| 58 | + * List(1, 2, 3).mapReduce(_ * 2)(_ + _) // res0: Int = 12 |
| 59 | + * "Yeah, well, you know, that's just, like, your opinion, man.".mapReduce(x => 1)(_ + _) // res1: Int = 59 |
| 60 | + *}}} |
| 61 | + * |
| 62 | + * Here, we begin by creating a class `ExtensionMethods` which contains our |
| 63 | + * `mapReduce` extension method. |
| 64 | + * |
| 65 | + * Note that `ExtensionMethods` takes a constructor argument `coll` of type `Repr`, where |
| 66 | + * `Repr` represents (typically) the collection type, and an argument `it` of a subtype of `IsIterable[Repr]`. |
| 67 | + * The body of the method starts by converting the `coll` argument to an `IterableOps` in order to |
| 68 | + * call the `iterator` method on it. |
| 69 | + * The remaining of the implementation is straightforward. |
| 70 | + * |
| 71 | + * The `withExtensions` implicit conversion makes the `mapReduce` operation available |
| 72 | + * on any type `Repr` for which it exists an implicit `IsIterable[Repr]` instance. |
| 73 | + * Note how we keep track of the precise type of the implicit `it` argument by using the |
| 74 | + * `it.type` singleton type, rather than the wider `IsIterable[Repr]` type. We do that |
| 75 | + * so that the information carried by the type members `A` and `C` of the `it` argument |
| 76 | + * is not lost. |
| 77 | + * |
| 78 | + * When the `mapReduce` method is called on some type of which it is not |
| 79 | + * a member, implicit search is triggered. Because implicit conversion |
| 80 | + * `withExtensions` is generic, it will be applied as long as an implicit |
| 81 | + * value of type `IsIterable[Repr]` can be found. Given that the |
| 82 | + * `IsIterable` companion object contains implicit members that return values of type |
| 83 | + * `IsIterable`, this requirement is typically satisfied, and the chain |
| 84 | + * of interactions described in the previous paragraph is set into action. |
| 85 | + * (See the `IsIterable` companion object, which contains a precise |
| 86 | + * specification of the available implicits.) |
| 87 | + * |
| 88 | + * ''Note'': Currently, it's not possible to combine the implicit conversion and |
| 89 | + * the class with the extension methods into an implicit class due to |
| 90 | + * limitations of type inference. |
| 91 | + * |
| 92 | + * ===Implementing `IsIterable` for New Types=== |
| 93 | + * |
| 94 | + * One must simply provide an implicit value of type `IsIterable` |
| 95 | + * specific to the new type, or an implicit conversion which returns an |
| 96 | + * instance of `IsIterable` specific to the new type. |
| 97 | + * |
| 98 | + * Below is an example of an implementation of the `IsIterable` trait |
| 99 | + * where the `Repr` type is `Range`. |
| 100 | + * |
| 101 | + *{{{ |
| 102 | + * implicit val rangeRepr: IsIterable[Range] { type A = Int; type C = IndexedSeq[Int] } = |
| 103 | + * new IsIterable[Range] { |
| 104 | + * type A = Int |
| 105 | + * type C = IndexedSeq[Int] |
| 106 | + * def apply(coll: Range): IterableOps[Int, IndexedSeq, IndexedSeq[Int]] = coll |
| 107 | + * } |
| 108 | + *}}} |
| 109 | + * |
| 110 | + * (Note that in practice the `IsIterable[Range]` instance is already provided by |
| 111 | + * the standard library, and it is defined as an `IsSeq[Range]` instance) |
| 112 | + */ |
| 113 | +trait IsIterable[Repr] extends IsIterableOnce[Repr] { |
| 114 | + |
| 115 | + /** The type returned by transformation operations that preserve the same elements |
| 116 | + * type (e.g. `filter`, `take`). |
| 117 | + * |
| 118 | + * In practice, this type is often `Repr` itself, excepted in the case |
| 119 | + * of `SeqView[A]` (and other `View[A]` subclasses), where it is “only” `View[A]`. |
| 120 | + */ |
| 121 | + type C |
| 122 | + |
| 123 | + @deprecated("'conversion' is now a method named 'apply'", "2.13.0") |
| 124 | + override val conversion: Repr => IterableOps[A, Iterable, C] = apply(_) |
| 125 | + |
| 126 | + /** A conversion from the type `Repr` to `IterableOps[A, Iterable, C]` */ |
| 127 | + def apply(coll: Repr): IterableOps[A, Iterable, C] |
| 128 | + |
| 129 | +} |
| 130 | + |
| 131 | +object IsIterable extends IsIterableLowPriority { |
| 132 | + |
| 133 | + // Straightforward case: IterableOps subclasses |
| 134 | + implicit def iterableOpsIsIterable[A0, CC0[X] <: IterableOps[X, Iterable, CC0[X]]]: IsIterable[CC0[A0]] { type A = A0; type C = CC0[A0] } = |
| 135 | + new IsIterable[CC0[A0]] { |
| 136 | + type A = A0 |
| 137 | + type C = CC0[A0] |
| 138 | + def apply(coll: CC0[A]): IterableOps[A, Iterable, C] = coll |
| 139 | + } |
| 140 | + |
| 141 | + // The `BitSet` type can not be unified with the `CC0` parameter of |
| 142 | + // the above definition because it does not take a type parameter. |
| 143 | + // Hence the need for a separate case: |
| 144 | + implicit def bitSetOpsIsIterable[C0 <: BitSet with BitSetOps[C0]]: IsIterable[C0] { type A = Int; type C = C0 } = |
| 145 | + new IsIterable[C0] { |
| 146 | + type A = Int |
| 147 | + type C = C0 |
| 148 | + def apply(coll: C0): IterableOps[Int, Iterable, C0] = coll |
| 149 | + } |
| 150 | + |
| 151 | +} |
| 152 | + |
| 153 | +trait IsIterableLowPriority { |
| 154 | + |
| 155 | + // Makes `IsSeq` instances visible in `IsIterable` companion |
| 156 | + implicit def isSeqLikeIsIterable[Repr](implicit |
| 157 | + isSeqLike: IsSeq[Repr] |
| 158 | + ): IsIterable[Repr] { type A = isSeqLike.A; type C = isSeqLike.C } = isSeqLike |
| 159 | + |
| 160 | + // Makes `IsMap` instances visible in `IsIterable` companion |
| 161 | + implicit def isMapLikeIsIterable[Repr](implicit |
| 162 | + isMapLike: IsMap[Repr] |
| 163 | + ): IsIterable[Repr] { type A = isMapLike.A; type C = isMapLike.C } = isMapLike |
| 164 | + |
| 165 | +} |
0 commit comments