Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.

Add Map operations. #137

Merged
merged 8 commits into from
Aug 9, 2017
Merged

Add Map operations. #137

merged 8 commits into from
Aug 9, 2017

Conversation

julienrf
Copy link
Contributor

No description provided.

@julienrf julienrf requested review from szeiger and Ichoran June 29, 2017 12:56
def iterableFactory: IterableFactory[Set] = Set
def empty: Set[K] = iterableFactory.empty
protected[this] def fromSpecificIterable(coll: Iterable[K]): Set[K] = iterableFactory.fromIterable(coll)
protected[this] def newSpecificBuilder(): Builder[K, Set[K]] = iterableFactory.newBuilder()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It’s a bit unfortunate that we have to implement all these methods.

* @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.
*/
def filterKeys(p: K => Boolean): CC[K, V @uncheckedVariance] = mapFromIterable(View.FilterKeys(coll, p))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that filterKeys and mapValues are not anymore lazy.

BTW, it’s good to see that their implementation is simpler than in the current collections!

Copy link
Contributor

Choose a reason for hiding this comment

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

We have to rename them, then. A difference in behavior like that can silently break code in unpleasant ways. It's annoying that they were always lazy, but we have to allow the user an opportunity to have every single spot pointed out to them. (We can auto-rewrite the ones that were obviously lazy-version-then-immediately-something-eager.)

Copy link
Contributor Author

@julienrf julienrf Jun 29, 2017

Choose a reason for hiding this comment

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

Otherwise, if we implement a lazy semantics what do you think of changing their return type to View[(K, V)] instead of CC[K, V]?

Copy link
Contributor

Choose a reason for hiding this comment

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

Switching it to a view isn't good either because it no longer has map semantics. You could switch it to a map view if we had such a thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can do .to(Map) to get back Map semantics. Or did you mean something else by “map semantics”?

Copy link
Contributor

@Ichoran Ichoran Jul 3, 2017

Choose a reason for hiding this comment

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

No, that doesn't work because it's not lazy any more. The output of filterKeys and mapValues is, for better or worse, a lazy map. Lazy-but-not-a-map is not a map. Map-but-not-lazy is not lazy. We can't know what properties people have relied upon in their code, so the only reasonable approach is to deprecate or remove the entire method and introduce another that does something that we think is less confusing, or maintain both the laziness and the map-ness.

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 think returning a View is fine, if people rely on Map-specific methods then they’ll get a compile error. I think the fact that filterKeys and mapValues return a “lazy Map” in the current collections can be a source of confusion and we should not support that in the strawman. Moreover, in the strawman we have a cleaner distinction between lazy and strict collections so we should take advantage of that.

Copy link
Contributor

Choose a reason for hiding this comment

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

So we're taking away the functionality completely, without a deprecation and no way to recover it, and not even changing the method name? Maybe it will be okay because nobody's using them for what they can do, but otherwise this is precisely the kind of thing that tends to upset users (unannounced breaking change with no workaround).

* @return a new $coll that contains all elements of the current $coll
* except one less occurrence of each of the elements of `elems`.
*/
def removeAll(keys: IterableOnce[K]): C = fromSpecificIterable(keys.iterator().foldLeft(coll)(_ - _))
Copy link
Contributor Author

@julienrf julienrf Jun 29, 2017

Choose a reason for hiding this comment

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

Dotty reports a type error here:

def removeAll(keys: IterableOnce[K]): C = fromSpecificIterable(keys.iterator().foldLeft(coll)(_ - _))
                                                                                              ^^^^^
                                                    found:    C
                                                    required: CC[K, V]

But I think he is wrong.
Indeed, the type of coll is CC[K, V], which is upper bounded by MapOps[K, V, CC, CC[K, V]] (note that the last parameter (whose formal name is C) is instantiated to CC[K, V]). Then, the - operator returns a C, which is a CC[K, V].

cc @odersky.

@@ -34,7 +34,7 @@ trait MapOps[K, +V, +CC[X, +Y] <: Map[X, Y] with MapOps[X, Y, CC, CC[X, Y]], +C
* @return a new $coll that contains all elements of the current $coll
* except one less occurrence of each of the elements of `elems`.
*/
def removeAll(keys: IterableOnce[K]): C = fromSpecificIterable(keys.iterator().foldLeft(coll)(_ - _))
def removeAll(keys: IterableOnce[K]): C = fromSpecificIterable(keys.iterator().foldLeft[CC[K, V]](coll)(_ - _))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this commit I try an alternative solution but I still get an error (different, though), with Dotty:

def removeAll(keys: IterableOnce[K]): C = fromSpecificIterable(keys.iterator().foldLeft[CC[K, V]](coll)(_ - _))
                                                                                                        ^^^
                                 value `-` is not a member of CC[K, V]

Which, again, seems wrong to me: CC is upper bounded by MapOps, which provides the - method.

@julienrf julienrf force-pushed the map-ops branch 3 times, most recently from 1351bc9 to 8df5083 Compare June 30, 2017 11:05
@julienrf
Copy link
Contributor Author

I’ll leave this PR for later because it has several compilation errors with dotty.

Copy link

@Dveim Dveim left a comment

Choose a reason for hiding this comment

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

What are reasons to have lazy mapValues method?

Current implementations don't distinguish view and don't rehash keys functionalities, what probably should not go as default. There are several cases where strict implementation (mapValuesNow) would have advantage. Why don't make it default behaviour?

@Ichoran
Copy link
Contributor

Ichoran commented Jul 17, 2017

@Dveim - The reason is to allow the strawman to function as a drop-in replacement for the existing collections. This has been a primary goal all along. Since the behavior changes radically when it is lazy vs. eager, simply swapping one for another isn't something you can do blind, even if it compiles. A lazy mapValues could be used to access a single element, making the eager version a million times slower. Alternatively, the lazy version could load a file every time on something that's accessed many times, making the lazy version ten thousand times slower. These kinds of speed differences are not just a performance detail, but the difference between working and effectively broken programs. So I don't think we can change the behavior without changing the method name so people at least have a chance to think about what's going on.

@Dveim
Copy link

Dveim commented Jul 17, 2017 via email

@amasson88
Copy link

@julienrf
Copy link
Contributor Author

If we keep the lazy behavior of mapValues, then IMHO its static return type should be View rather than Map (and we should probably have a MapView[K, V] type of view with Map-like operations on it).

Then, do we want a strict version? We can always write xs.mapValues(f).to(HashMap) to force the evaluation.

Also, I didn’t carefully check but I think in my projects my main usage of mapValues is to transform the values of a groupBy result. However, I think we will have another variant of groupBy that will combine groupBy and (a strict version of) mapValues (see #42). So, if that corresponds to the main usage of this method for the majority of the community too, then I don’t think we need another a strict variant of mapValues.

@julienrf
Copy link
Contributor Author

I rebased to master and bumped the dotty version to take advantage of scala/scala3#2889 but we now have other compilation errors:

[error] -- [E045] Syntax Error: /home/julien/workspace/dev/scala/collection-strawman/src/test/scala/strawman/collection/test/GenericTest.scala:54:38 
[error] 54 |    Parse.parseCollection[(Int, Int), immutable.HashMap[Int, Int]](immutable.HashMap).parse("1-2|3-4").contains(immutable.HashMap((1, 2), (3, 4)))
[error]    |                                      ^^^^^^^^^
[error]    |                                      cyclic reference involving type CC
[error] -- [E008] Member Not Found Error: /home/julien/workspace/dev/scala/collection-strawman/src/test/scala/strawman/collection/test/Test.scala:498:47 
[error] 498 |    val xs7 = (xs: immutable.Map[String, Int]) map ({ case (k, v) => (new Foo, v) }: scala.PartialFunction[(String, Int), (Foo, Int)])
[error]     |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]     |value `map` is not a member of strawman.collection.immutable.Map[java.lang.String, scala.Int]

@julienrf
Copy link
Contributor Author

Dotty fix in progress: scala/scala3#2914 😄

@julienrf julienrf merged commit e7bc050 into master Aug 9, 2017
@julienrf julienrf deleted the map-ops branch August 9, 2017 14:42
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants