scala.collection.generic.IsIterable.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-library Show documentation
Show all versions of scala-library Show documentation
Standard library for the Scala Programming Language
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.collection
package generic
/** A trait which can be used to avoid code duplication when defining extension
* methods that should be applicable both to existing Scala collections (i.e.,
* types extending `Iterable`) as well as other (potentially user-defined)
* types that could be converted to a Scala collection type. This trait
* makes it possible to treat Scala collections and types that can be implicitly
* converted to a collection type uniformly. For example, one can provide
* extension methods that work both on collection types and on `String`s (`String`s
* do not extend `Iterable`, but can be converted to `Iterable`)
*
* `IsIterable` provides three members:
*
* 1. type member `A`, which represents the element type of the target `Iterable[A]`
* 1. type member `C`, which represents the type returned by transformation operations that preserve the collection’s elements type
* 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]`.
*
* ===Usage===
*
* One must provide `IsIterable` as an implicit parameter type of an implicit
* conversion. Its usage is shown below. Our objective in the following example
* is to provide a generic extension method `mapReduce` to any type that extends
* or can be converted to `Iterable`. In our example, this includes
* `String`.
*
* {{{
* import scala.collection.{Iterable, IterableOps}
* import scala.collection.generic.IsIterable
*
* class ExtensionMethods[Repr, I <: IsIterable[Repr]](coll: Repr, it: I) {
* def mapReduce[B](mapper: it.A => B)(reducer: (B, B) => B): B = {
* val iter = it(coll).iterator
* var res = mapper(iter.next())
* while (iter.hasNext)
* res = reducer(res, mapper(iter.next()))
* res
* }
* }
*
* implicit def withExtensions[Repr](coll: Repr)(implicit it: IsIterable[Repr]): ExtensionMethods[Repr, it.type] =
* new ExtensionMethods(coll, it)
*
* // See it in action!
* List(1, 2, 3).mapReduce(_ * 2)(_ + _) // res0: Int = 12
* "Yeah, well, you know, that's just, like, your opinion, man.".mapReduce(x => 1)(_ + _) // res1: Int = 59
*}}}
*
* Here, we begin by creating a class `ExtensionMethods` which contains our
* `mapReduce` extension method.
*
* Note that `ExtensionMethods` takes a constructor argument `coll` of type `Repr`, where
* `Repr` represents (typically) the collection type, and an argument `it` of a subtype of `IsIterable[Repr]`.
* The body of the method starts by converting the `coll` argument to an `IterableOps` in order to
* call the `iterator` method on it.
* The remaining of the implementation is straightforward.
*
* The `withExtensions` implicit conversion makes the `mapReduce` operation available
* on any type `Repr` for which it exists an implicit `IsIterable[Repr]` instance.
* Note how we keep track of the precise type of the implicit `it` argument by using the
* `it.type` singleton type, rather than the wider `IsIterable[Repr]` type. We do that
* so that the information carried by the type members `A` and `C` of the `it` argument
* is not lost.
*
* When the `mapReduce` method is called on some type of which it is not
* a member, implicit search is triggered. Because implicit conversion
* `withExtensions` is generic, it will be applied as long as an implicit
* value of type `IsIterable[Repr]` can be found. Given that the
* `IsIterable` companion object contains implicit members that return values of type
* `IsIterable`, this requirement is typically satisfied, and the chain
* of interactions described in the previous paragraph is set into action.
* (See the `IsIterable` companion object, which contains a precise
* specification of the available implicits.)
*
* ''Note'': Currently, it's not possible to combine the implicit conversion and
* the class with the extension methods into an implicit class due to
* limitations of type inference.
*
* ===Implementing `IsIterable` for New Types===
*
* One must simply provide an implicit value of type `IsIterable`
* specific to the new type, or an implicit conversion which returns an
* instance of `IsIterable` specific to the new type.
*
* Below is an example of an implementation of the `IsIterable` trait
* where the `Repr` type is `Range`.
*
*{{{
* implicit val rangeRepr: IsIterable[Range] { type A = Int; type C = IndexedSeq[Int] } =
* new IsIterable[Range] {
* type A = Int
* type C = IndexedSeq[Int]
* def apply(coll: Range): IterableOps[Int, IndexedSeq, IndexedSeq[Int]] = coll
* }
*}}}
*
* (Note that in practice the `IsIterable[Range]` instance is already provided by
* the standard library, and it is defined as an `IsSeq[Range]` instance)
*/
trait IsIterable[Repr] extends IsIterableOnce[Repr] {
/** The type returned by transformation operations that preserve the same elements
* type (e.g. `filter`, `take`).
*
* In practice, this type is often `Repr` itself, excepted in the case
* of `SeqView[A]` (and other `View[A]` subclasses), where it is “only” `View[A]`.
*/
type C
@deprecated("'conversion' is now a method named 'apply'", "2.13.0")
override val conversion: Repr => IterableOps[A, Iterable, C] = apply(_)
/** A conversion from the type `Repr` to `IterableOps[A, Iterable, C]` */
def apply(coll: Repr): IterableOps[A, Iterable, C]
}
object IsIterable extends IsIterableLowPriority {
// Straightforward case: IterableOps subclasses
implicit def iterableOpsIsIterable[A0, CC0[X] <: IterableOps[X, Iterable, CC0[X]]]: IsIterable[CC0[A0]] { type A = A0; type C = CC0[A0] } =
new IsIterable[CC0[A0]] {
type A = A0
type C = CC0[A0]
def apply(coll: CC0[A]): IterableOps[A, Iterable, C] = coll
}
// The `BitSet` type can not be unified with the `CC0` parameter of
// the above definition because it does not take a type parameter.
// Hence the need for a separate case:
implicit def bitSetOpsIsIterable[C0 <: BitSet with BitSetOps[C0]]: IsIterable[C0] { type A = Int; type C = C0 } =
new IsIterable[C0] {
type A = Int
type C = C0
def apply(coll: C0): IterableOps[Int, Iterable, C0] = coll
}
}
trait IsIterableLowPriority {
// Makes `IsSeq` instances visible in `IsIterable` companion
implicit def isSeqLikeIsIterable[Repr](implicit
isSeqLike: IsSeq[Repr]
): IsIterable[Repr] { type A = isSeqLike.A; type C = isSeqLike.C } = isSeqLike
// Makes `IsMap` instances visible in `IsIterable` companion
implicit def isMapLikeIsIterable[Repr](implicit
isMapLike: IsMap[Repr]
): IsIterable[Repr] { type A = isMapLike.A; type C = isMapLike.C } = isMapLike
}