-
Notifications
You must be signed in to change notification settings - Fork 3.1k
In collections API, deprecate symbolic methods with multiple parameters #6719
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
Conversation
|
|
||
| /** Alias for `addOne` */ | ||
| @`inline` final def += (elem: A): this.type = addOne(elem) | ||
| @`inline` def += (elem: A): this.type = addOne(elem) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to remove final here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to override it (see Dale's comment)
| @deprecated("Use `+= ((k, v))` instead of `+= (k, v)`", "2.13.0") | ||
| def +=(key: Long, value: V): this.type = { update(key, value); this } | ||
|
|
||
| @inline override final def +=(kv: (Long, V)): this.type = { update(kv._1, kv._2); this } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this already forward to the addOne below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it forwards to the same method (in dynamic dispatch) but it doesn't inline the overridden method where the concrete types are known so that the optimizer can unbox.
|
Some test cases show inliner warnings in the PR builds but I cannot reproduce them locally. |
|
It looks like the It's not clear to me why the tests only fail if you compile the standard library with optimizations. This should be depend on whether or not the tests themselves are compiled with optimizations, regardless of the standard library. |
f5c0a31 to
a4e4bff
Compare
It's not entirely pointless. The method can still be inlined if the receiver is final But for example |
I agree this should not make a difference. Are you sure that's what you were observing / that you had the flags on locally? |
|
This override of trait Growable[-A] extends Clearable {
def addOne(elem: A): this.type
@`inline` final def += (elem: A): this.type = addOne(elem)
}
final class LongMap[V] extends ... {
@inline
override def addOne(kv: (Long, V)): this.type = { update(kv._1, kv._2); this }
}This should be enough for the optimizer to be able to optimize val longMap: LongMap[T] = ...
longMap += 1L -> tbecause first val tuple = (box(1L), t)
longMap.addOne(tuple)Now val tuple = (box(1L), t)
longMap.update(unbox(tuple._1), tuple._2)
longMap
val tuple_1 = box(1L)
val tuple_2 = t
longMap.update(unbox(tuple_1), tuple_2)
longMapand finally the box can be removed: val tuple_1 = 1L
val tuple_2 = t
longMap.update(tuple_1, tuple_2)
longMapAll of this is straightforward. Why on earth does the optimizer need the override of |
|
The inliner does only one round currently, so sees |
|
Ouch. That's ... disappointing. It should be fixable, though. So I don't think we should expose a bad public API to work around it. |
|
Yes, agree, on both points :) |
|
Improving the optimizer in this way should also solve @odd's problem of not being able to specialize the symbolic aliases in specialized collection subclasses. |
|
Can we merge this PR? |
|
I'm working on an improvement to the optimizer so it will handle this case, so we can remove the override. |
|
Marking as |
|
Similarly to my comment on #6584, I'd like to suggest to not merge changes like that unless they are approved by the SIP committee. Deprecations may seem harmless, but for folks who use |
|
Tupling should be truly zero-overhead before we consider this. Then Also, the rule that symbolic methods are just methods is an exceedingly useful one. That they're "just methods" except this rule and that exception and whatever this other thing is isn't something I look forward to explaining to people, or remembering myself. So I'm against the change in general. But without even a sensible refactoring to preserve the semantics of high-performance code (without doing Java-style mangling of operations) seems like it's creating considerable hardship for very little benefit. |
There are no language changes here, we're improving the encoding of But after looking at these changes again, deprecating the two-argument version of I still think we should deprecate the varargs version to avoid the confusion when calling it with 2 arguments on a
Don't use
The inlinable version in this PR has zero overhead. The optimizer can eliminate the tupling. The current version increases the cost of other uses of
Note that only left-associative infix operators are currently allowed to have multiple parameters. All other operators (prefix, postfix, right-associative infix) already have similar restrictions on the number of parameters. |
|
The two-arg version exists on AnyRefMap. Given concerns about inlining breaking separate compilation, should we count on optimizations that require inlining? Anyway, a more adept optimizer is certainly welcome! I hadn't realized that the restriction was suggested for all infix operators, not just symbolic ones. If universally applied like that, my consistency argument is invalid. On the other hand, it strengthens the "this is a major hassle" argument. If there is decent evidence that multi-arg infix is confusing people in a way that doesn't have anything to do with tuples, then I agree it should go. Otherwise I don't understand the downside of allowing it. Maybe add a lint rule, but why make people change a bunch of their code if it's not something that induces mistakes? (The people who wrote it probably wouldn't have written it that way if it didn't seem clearer to them, so a priori one should assume that at least the original coder will find the new code harder to understand.) |
My bad - I overlooked this. Somehow, I thought that there was a pull request that deprecates all multiple-argument operators, but this pull request is definitely not that.
It looks like this boils down to a difference in perception. Indeed, if we consider all -X flags to be features that don't come with any compatibility guarantees, then compatibility concerns don't apply to them. @jvican I think that it would be useful to discuss what compatibility guarantees we're planning to uphold during the transition period from Scala 2 to Scala 3. What would be an appropriate way to bring this up at a SIP meeting? |
The best way would be to open a thread in our SIP meeting group in Scala Contributors so that other people can chime in before the meeting, and then we can quickly follow up on that discussion in the SIP meeting and schedule a proper day to discuss that. |
|
@Ichoran Ah, right, the two-argument version being deprecated here is only in |
- Deprecate the varargs version of `Growable.+=`. It is confusing when used with tuples (e.g. for `Map` types) and Scala 3.0 will probably drop support for infix operators with multiple parameters. - Add back the specialized two-argument `+=` to `AnyRefMap` which was present in 2.12. - There is no need to deprecate the two-argument versions now. If a future Scala version disallows calling them with operator syntax, these calls should automatically fall back to the tupled versions. - Make the tupled `addOne` methods in `AnyRefMap` and `LongMap` inlinable. When inlining is enabled the optimizer can elide the tupling. Upcoming inliner improvements should also enable this when `addOne` is called via the also inlinable `+=` alias.
|
Updated:
|
|
@szeiger Is this ready to be merged (and make M5)? Can it lose its WIP label? |
Scala 3.0 will probably drop support for infix operators with multiple
parameters.
Fixes scala/bug#10828