Skip to content

Commit

Permalink
Add MapView to stdlib
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Nov 1, 2023
1 parent 0eb57a7 commit 8c0c2c9
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 5 deletions.
12 changes: 7 additions & 5 deletions tests/pos-special/stdlib/collection/Map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import scala.collection.generic.DefaultSerializable
import scala.collection.mutable.StringBuilder
import scala.util.hashing.MurmurHash3
import language.experimental.captureChecking
import caps.unsafe.unsafeAssumePure

/** Base Map type */
trait Map[K, +V]
Expand Down Expand Up @@ -103,8 +104,9 @@ trait Map[K, +V]
trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
extends IterableOps[(K, V), Iterable, C]
with PartialFunction[K, V] {
this: MapOps[K, V, CC, C]^ =>

override def view: MapView[K, V] = new MapView.Id(this)
override def view: MapView[K, V]^{this} = new MapView.Id(this)

/** Returns a [[Stepper]] for the keys of this map. See method [[stepper]]. */
def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S = {
Expand Down Expand Up @@ -253,15 +255,15 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
* the predicate `p`. The resulting map wraps the original map without copying any elements.
*/
@deprecated("Use .view.filterKeys(f). A future version will include a strict version of this method (for now, .view.filterKeys(p).toMap).", "2.13.0")
def filterKeys(p: K => Boolean): MapView[K, V] = new MapView.FilterKeys(this, p)
def filterKeys(p: K => Boolean): MapView[K, V]^{this, p} = new MapView.FilterKeys(this, p)

/** Transforms this map by applying a function to every retrieved value.
* @param f the function used to transform values of this map.
* @return a map view which maps every key of this map
* to `f(this(key))`. The resulting map wraps the original map without copying any elements.
*/
@deprecated("Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).", "2.13.0")
def mapValues[W](f: V => W): MapView[K, W] = new MapView.MapValues(this, f)
def mapValues[W](f: V => W): MapView[K, W]^{this, f} = new MapView.MapValues(this, f)

/** Defines the default value computation for the map,
* returned when a key is not found
Expand Down Expand Up @@ -354,7 +356,7 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
@deprecated("Consider requiring an immutable Map.", "2.13.0")
@`inline` def -- (keys: IterableOnce[K]^): C = {
lazy val keysSet = keys.iterator.to(immutable.Set)
fromSpecific(this.view.filterKeys(k => !keysSet.contains(k)))
fromSpecific(this.view.filterKeys(k => !keysSet.contains(k))).unsafeAssumePure
}

@deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0")
Expand All @@ -375,7 +377,7 @@ object MapOps {
*/
@SerialVersionUID(3L)
class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[_, AnyConstr, _]](
self: MapOps[K, V, CC, _] with IterableOps[(K, V), IterableCC, _],
self: (MapOps[K, V, CC, _] with IterableOps[(K, V), IterableCC, _])^,
p: ((K, V)) => Boolean
) extends IterableOps.WithFilter[(K, V), IterableCC](self, p) with Serializable {

Expand Down
196 changes: 196 additions & 0 deletions tests/pos-special/stdlib/collection/MapView.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* 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

import scala.annotation.nowarn
import scala.collection.MapView.SomeMapOps
import scala.collection.mutable.Builder
import language.experimental.captureChecking
import caps.unsafe.unsafeAssumePure

trait MapView[K, +V]
extends MapOps[K, V, ({ type l[X, Y] = View[(X, Y)] })#l, View[(K, V)]]
with View[(K, V)] {
this: MapView[K, V]^ =>

override def view: MapView[K, V]^{this} = this

// Ideally this returns a `View`, but bincompat
/** Creates a view over all keys of this map.
*
* @return the keys of this map as a view.
*/
override def keys: Iterable[K]^{this} = new MapView.Keys(this)

// Ideally this returns a `View`, but bincompat
/** Creates a view over all values of this map.
*
* @return the values of this map as a view.
*/
override def values: Iterable[V]^{this} = new MapView.Values(this)

/** Filters this map by retaining only keys satisfying a predicate.
* @param p the predicate used to test keys
* @return an immutable map consisting only of those key value pairs of this map where the key satisfies
* the predicate `p`. The resulting map wraps the original map without copying any elements.
*/
override def filterKeys(p: K => Boolean): MapView[K, V]^{this, p} = new MapView.FilterKeys(this, p)

/** Transforms this map by applying a function to every retrieved value.
* @param f the function used to transform values of this map.
* @return a map view which maps every key of this map
* to `f(this(key))`. The resulting map wraps the original map without copying any elements.
*/
override def mapValues[W](f: V => W): MapView[K, W]^{this, f} = new MapView.MapValues(this, f)

override def filter(pred: ((K, V)) => Boolean): MapView[K, V]^{this, pred} = new MapView.Filter(this, false, pred)

override def filterNot(pred: ((K, V)) => Boolean): MapView[K, V]^{this, pred} = new MapView.Filter(this, true, pred)

override def partition(p: ((K, V)) => Boolean): (MapView[K, V]^{this, p}, MapView[K, V]^{this, p}) = (filter(p), filterNot(p))

override def tapEach[U](f: ((K, V)) => U): MapView[K, V]^{this, f} = new MapView.TapEach(this, f)

def mapFactory: MapViewFactory = MapView

override def empty: MapView[K, V] = mapFactory.empty

override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, View, ({ type l[X, Y] = View[(X, Y)] })#l]^{this, p} = new MapOps.WithFilter(this, p)

override def toString: String = super[View].toString

@nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""")
override protected[this] def stringPrefix: String = "MapView"
}

object MapView extends MapViewFactory {

/** An `IterableOps` whose collection type and collection type constructor are unknown */
type SomeIterableConstr[X, Y] = IterableOps[_, AnyConstr, _]
/** A `MapOps` whose collection type and collection type constructor are (mostly) unknown */
type SomeMapOps[K, +V] = MapOps[K, V, SomeIterableConstr, _]

@SerialVersionUID(3L)
object EmptyMapView extends AbstractMapView[Any, Nothing] {
// !!! cc problem: crash when we replace the line with
// private val EmptyMapView: MapView[Any, Nothing] = new AbstractMapView[Any, Nothing] {
override def get(key: Any): Option[Nothing] = None
override def iterator: Iterator[Nothing] = Iterator.empty[Nothing]
override def knownSize: Int = 0
override def isEmpty: Boolean = true
override def filterKeys(p: Any => Boolean): MapView[Any, Nothing] = this
override def mapValues[W](f: Nothing => W): MapView[Any, Nothing] = this
override def filter(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this
override def filterNot(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this
override def partition(p: ((Any, Nothing)) => Boolean): (MapView[Any, Nothing], MapView[Any, Nothing]) = (this, this)
}

@SerialVersionUID(3L)
class Id[K, +V](underlying: SomeMapOps[K, V]^) extends AbstractMapView[K, V] {
def get(key: K): Option[V] = underlying.get(key)
def iterator: Iterator[(K, V)]^{this} = underlying.iterator
override def knownSize: Int = underlying.knownSize
override def isEmpty: Boolean = underlying.isEmpty
}

// Ideally this is public, but bincompat
@SerialVersionUID(3L)
private class Keys[K](underlying: SomeMapOps[K, _]^) extends AbstractView[K] {
def iterator: Iterator[K]^{this} = underlying.keysIterator
override def knownSize: Int = underlying.knownSize
override def isEmpty: Boolean = underlying.isEmpty
}

// Ideally this is public, but bincompat
@SerialVersionUID(3L)
private class Values[+V](underlying: SomeMapOps[_, V]^) extends AbstractView[V] {
def iterator: Iterator[V]^{this} = underlying.valuesIterator
override def knownSize: Int = underlying.knownSize
override def isEmpty: Boolean = underlying.isEmpty
}

@SerialVersionUID(3L)
class MapValues[K, +V, +W](underlying: SomeMapOps[K, V]^, f: V => W) extends AbstractMapView[K, W] {
def iterator: Iterator[(K, W)]^{this} = underlying.iterator.map(kv => (kv._1, f(kv._2)))
def get(key: K): Option[W] = underlying.get(key).map(f)
override def knownSize: Int = underlying.knownSize
override def isEmpty: Boolean = underlying.isEmpty
}

@SerialVersionUID(3L)
class FilterKeys[K, +V](underlying: SomeMapOps[K, V]^, p: K => Boolean) extends AbstractMapView[K, V] {
def iterator: Iterator[(K, V)]^{this} = underlying.iterator.filter { case (k, _) => p(k) }
def get(key: K): Option[V] = if (p(key)) underlying.get(key) else None
override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize
override def isEmpty: Boolean = iterator.isEmpty
}

@SerialVersionUID(3L)
class Filter[K, +V](underlying: SomeMapOps[K, V]^, isFlipped: Boolean, p: ((K, V)) => Boolean) extends AbstractMapView[K, V] {
def iterator: Iterator[(K, V)]^{this} = underlying.iterator.filterImpl(p, isFlipped)
def get(key: K): Option[V] = underlying.get(key) match {
case s @ Some(v) if p((key, v)) != isFlipped => s
case _ => None
}
override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize
override def isEmpty: Boolean = iterator.isEmpty
}

@SerialVersionUID(3L)
class TapEach[K, +V, +U](underlying: SomeMapOps[K, V]^, f: ((K, V)) => U) extends AbstractMapView[K, V] {
override def get(key: K): Option[V] = {
underlying.get(key) match {
case s @ Some(v) =>
f((key, v))
s
case None => None
}
}
override def iterator: Iterator[(K, V)]^{this} = underlying.iterator.tapEach(f)
override def knownSize: Int = underlying.knownSize
override def isEmpty: Boolean = underlying.isEmpty
}

override def newBuilder[sealed X, sealed Y]: Builder[(X, Y), MapView[X, Y]] = mutable.HashMap.newBuilder[X, Y].mapResult(_.view)

override def empty[K, V]: MapView[K, V] = EmptyMapView.asInstanceOf[MapView[K, V]]

override def from[K, V](it: IterableOnce[(K, V)]^): View[(K, V)] =
View.from(it).unsafeAssumePure
// unsafeAssumePure needed here since MapViewFactory inherits from MapFactory,
// and the latter assumes maps are strict, so from's result captures nothing.

override def from[K, V](it: SomeMapOps[K, V]^): MapView[K, V]^{it} = it match {
case mv: MapView[K, V] => mv
case other => new MapView.Id(other)
}

override def apply[K, V](elems: (K, V)*): MapView[K, V] = from(elems.toMap)
}

trait MapViewFactory extends collection.MapFactory[({ type l[X, Y] = View[(X, Y)]})#l] {

def newBuilder[X, Y]: Builder[(X, Y), MapView[X, Y]]

def empty[X, Y]: MapView[X, Y]

def from[K, V](it: SomeMapOps[K, V]^): MapView[K, V]^{it}

override def apply[K, V](elems: (K, V)*): MapView[K, V] = from(elems.toMap)
}

/** Explicit instantiation of the `MapView` trait to reduce class file size in subclasses. */
@SerialVersionUID(3L)
abstract class AbstractMapView[K, +V] extends AbstractView[(K, V)] with MapView[K, V]:
this: AbstractMapView[K, V]^ =>

0 comments on commit 8c0c2c9

Please sign in to comment.