Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make MapView#values preserve laziness #9090

Merged
merged 1 commit into from Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.sbt
Expand Up @@ -394,6 +394,10 @@ val mimaFilterSettings = Seq {
ProblemFilters.exclude[MissingTypesProblem]("scala.collection.convert.JavaCollectionWrappers$JPropertiesWrapper"),
ProblemFilters.exclude[MissingTypesProblem]("scala.collection.convert.JavaCollectionWrappers$JSetWrapper"),
ProblemFilters.exclude[MissingTypesProblem]("scala.collection.mutable.WeakHashMap"),

// Fix for scala/bug#12059
ProblemFilters.exclude[MissingClassProblem]("scala.collection.MapView$Keys"),
ProblemFilters.exclude[MissingClassProblem]("scala.collection.MapView$Values"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to avoid making these jvm-public classes by creating anonymous inner classes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. I see your point, but at the same time, I think it's cleaner the way it is. I'd love to know what others think cc @julienrf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference for having (private) named classes

),
}

Expand Down
30 changes: 30 additions & 0 deletions src/library/scala/collection/MapView.scala
Expand Up @@ -21,6 +21,20 @@ trait MapView[K, +V]

override def view: MapView[K, V] = 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] = 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] = 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
Expand Down Expand Up @@ -82,6 +96,22 @@ object MapView extends MapViewFactory {
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] = 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] = 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)] = underlying.iterator.map(kv => (kv._1, f(kv._2)))
Expand Down
32 changes: 32 additions & 0 deletions test/junit/scala/collection/MapViewTest.scala
Expand Up @@ -8,6 +8,7 @@ class MapViewTest {
def _toString(): Unit = {
assertEquals("MapView(<not computed>)", Map(1 -> 2).view.toString)
}

@deprecated("Tests deprecated API", since="2.13")
@Test
def testStringPrefixToString(): Unit = {
Expand All @@ -18,6 +19,7 @@ class MapViewTest {
}
assertEquals("FooMapView(<not computed>)", mapView.toString)
}

@Test
def testClassNameToString(): Unit = {
val mapView = new collection.MapView[Int,Int] {
Expand All @@ -27,4 +29,34 @@ class MapViewTest {
}
assertEquals("FooMapView(<not computed>)", mapView.toString)
}

@Test
def testKeysIsLazy(): Unit = {
var counter = 0
def assertLazy(): Unit = assertEquals(0, counter)

val map = (1 to 10).map(i => i -> i).toMap
val mapView = map.view.filterKeys(_ => { counter += 1; true })
assertLazy()
val keys = mapView.keys
assert(keys.isInstanceOf[View[_]])
assertLazy()
val _ = keys.map(_ + 1)
assertLazy()
}

@Test
def testValuesIsLazy(): Unit = {
var counter = 0
def assertLazy(): Unit = assertEquals(0, counter)

val map = (1 to 10).map(i => i -> i).toMap
val mapView = map.view.mapValues(i => { counter += 1; i })
assertLazy()
val values = mapView.values
assert(values.isInstanceOf[View[_]])
assertLazy()
val _ = values.map(_ + 1)
assertLazy()
}
}