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
List(1.0, -1.0).sorted gives a deprecation warning #11844
Comments
@NthPortal wrote:
|
@odersky wrote: I believe we should simply keep the old behavior. My opinion about NaN and -0.0 is:
My rationale is that these constructs are illogical anyway. No matter what you do, there will be weirdness. So, the best thing to do is not upset the apple cart. |
@NthPortal wrote: thoughts @Ichoran @szeiger @SethTisue? |
@NthPortal wrote: FWIW, Java uses a total ordering on primitives: scala> Array[Double](5.0, 3.0, NaN, 4.0)
res0: Array[Double] = Array(5.0, 3.0, NaN, 4.0)
scala> java.util.Arrays.sort(res0)
scala> res0
res3: Array[Double] = Array(3.0, 4.0, 5.0, NaN) |
@NthPortal wrote: Personally, I think we have an obligation to have Another possibility of course is to have the default ordering just throw an exception if you give it If we must un-deprecate it, I feel the default should be the total ordering. I don't care particularly about how |
@odersky wrote: Java uses total ordering right? So why and when did Scala deviate from this to use IEEE ordering? |
@NthPortal wrote: @odersky it changed in #5104 and scala/scala#76 In essence the issue is that there is an expectation (a valid one) that I highly recommend reading all of the discussions on scala/scala#6323 and scala/scala#6410 (even though they're long) for the full context of how we decided on the solution we chose. |
@dwijnand wrote: It's a shame that Java is inconsistent between Can we capture this explanation and put it in on a page of docs.scala-lang.org and refer to it in the Scaladoc? Or put the whole explanation in the Scaladoc. Note @som-snytt had some thoughts around those docs in scala/scala#8191 (comment) too. |
@odersky wrote: I have already expressed my opinion. The fact that it stopped working, despite the fact that a lot of eyes looked at it, is worrying to me. Did anyone think it was acceptable that this would cause an implicit ambiguity message? Or did it just slip through with nobody realizing the consequences? |
@odersky wrote:
|
@dwijnand wrote:
|
@odersky wrote: I agree it's good to document, but any such documentation cannot be a reason to keep the current broken behavior. For background: I came across this because a professor of mathematics was stumped by the behavior and did not know how to proceed. |
The biggest problem here, by far, is that we're abusing deprecations to provide an informational message. If the thing said, always, and only for min/max/gt etc., not
and on web interfaces, this was reduced to a popup, then there's no problem. But right now, we get a weird cryptic message about deprecations, without showing the deprecation, on an utterly normal-looking piece of code. On the other hand, which behavior is followed can be critical for the correct operation of numeric code, yet difficult to debug when behavior changes, so I don't think we can just go changing it without giving users some indication that they need to examine the code. The right solution is to introduce another trait, This isn't binary compatible, and even then it's inconsistent with 2.13 behavior, but it matches 2.12 behavior and follows the "principle of least surprise" (i.e. things work the same way). A pragmatic solution might be just to make |
So here are some proposed criteria of success:
Is there a solution that fit this bill? |
Disgusting, but it fits the bill except for the fact that sorting behavior is still not the same as 2.12 (but I believe we all agree that the behavior of sorting in 2.12 was utterly broken, and that the new behavior is better). Better the solution in a future where we can break bin compat: rewrite |
summary
Edit:
java.util.Arrays sorting primitive doublescala> import java.lang.Double.NaN
import java.lang.Double.NaN
scala> val xs = Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0); java.util.Arrays.sort(xs); xs
xs: Array[Double] = Array(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
res0: Array[Double] = Array(-0.0, 0.0, 1.0, 2.0, 3.0, NaN) java.util.Collections sorting boxed java.lang.Doublescala> val ys = java.util.Arrays.asList(2.0: java.lang.Double, 1.0: java.lang.Double, java.lang.Double.NaN: java.lang.Double, 3.0: java.lang.Double, 0.0: java.lang.Double, -0.0: java.lang.Double); java.util.Collections.sort(ys); ys
ys: java.util.List[Double] = [-0.0, 0.0, 1.0, 2.0, 3.0, NaN]
res4: java.util.List[Double] = [-0.0, 0.0, 1.0, 2.0, 3.0, NaN]
scala> java.util.Collections.max(ys)
res5: Double = NaN
scala> java.util.Collections.min(ys)
res6: Double = -0.0 java.lang.Mathscala> java.lang.Math.max(2.0, NaN)
res8: Double = NaN
scala> java.lang.Math.min(2.0, NaN)
res9: Double = NaN Scala 2.12.10[info] Starting scala interpreter...
Welcome to Scala 2.12.10 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> import java.lang.Double.NaN
import java.lang.Double.NaN
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
res0: Array[Double] = Array(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
res1: Double = 3.0
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
res2: Double = 0.0
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
res3: List[Double] = List(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
res4: Double = 3.0
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
res5: Double = 0.0
scala> 2.0 max NaN
res6: Double = NaN
scala> 2.0 min NaN
res7: Double = NaN
scala> :paste
// Entering paste mode (ctrl-D to finish)
def typeclassMax[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a max aa
}
def typeclassMin[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a min aa
}
// Exiting paste mode, now interpreting.
typeclassMax: [A](a: A, aa: A)(implicit evidence$1: Ordering[A])A
typeclassMin: [A](a: A, aa: A)(implicit evidence$2: Ordering[A])A
scala> typeclassMax(2.0, NaN)
res0: Double = NaN
scala> typeclassMin(2.0, NaN)
res1: Double = NaN Scala 2.13.1Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> import java.lang.Double.NaN
import java.lang.Double.NaN
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res0: Array[Double] = Array(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res1: Double = NaN
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res2: Double = -0.0
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res3: List[Double] = List(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res4: Double = NaN
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res5: Double = -0.0
scala> 2.0 max NaN
res6: Double = NaN
scala> 2.0 min NaN
res7: Double = NaN
scala> :paste
// Entering paste mode (ctrl-D to finish)
def typeclassMax[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a max aa
}
def typeclassMin[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a min aa
}
// Exiting paste mode, now interpreting.
typeclassMax: [A](a: A, aa: A)(implicit evidence$1: Ordering[A])A
typeclassMin: [A](a: A, aa: A)(implicit evidence$2: Ordering[A])A
scala> typeclassMax(2.0, NaN)
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res8: Double = NaN
scala> typeclassMin(2.0, NaN)
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res9: Double = 2.0 Scala 2.13.1 + IEEE orderingWelcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> import Ordering.Double.IeeeOrdering
import Ordering.Double.IeeeOrdering
scala> import java.lang.Double.NaN
import java.lang.Double.NaN
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
res0: Array[Double] = Array(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
res1: Double = NaN
scala> Array(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
res2: Double = NaN
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).sorted
res3: List[Double] = List(-0.0, 0.0, 1.0, 2.0, 3.0, NaN)
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).max
res4: Double = NaN
scala> List(2.0, 1.0, NaN, 3.0, 0.0, -0.0).min
res5: Double = NaN
scala> 2.0 max NaN
res6: Double = NaN
scala> 2.0 min NaN
res7: Double = NaN
scala> :paste
// Entering paste mode (ctrl-D to finish)
def typeclassMax[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a max aa
}
def typeclassMin[A: Ordering](a: A, aa: A): A = {
val ord = implicitly[Ordering[A]]
import ord._
a min aa
}
// Exiting paste mode, now interpreting.
typeclassMax: [A](a: A, aa: A)(implicit evidence$1: Ordering[A])A
typeclassMin: [A](a: A, aa: A)(implicit evidence$2: Ordering[A])A
scala> typeclassMax(2.0, NaN)
res8: Double = NaN
scala> typeclassMin(2.0, NaN)
res9: Double = NaN |
I agree that just undeprecating
@Ichoran gave me an example code. scala> def typeclassMin[A: Ordering](a: A, aa: A): A = {
| val ord = implicitly[Ordering[A]]
| import ord._
| a min aa
| }
typeclassMin: [A](a: A, aa: A)(implicit evidence$1: Ordering[A])A
scala> typeclassMin(2.0, NaN)
warning: there was one deprecation warning (since 2.13.0); for details, enable `:setting -deprecation' or `:replay -deprecation'
res1: Double = 2.0
I think the surprising thing about this is that |
I wouldn't count on it. |
@martijnhoekstra You're right. I guess then rename to |
Personally, I would see more in an |
Like it or not, the IEEE754 implementation of floating point numbers is ubiquitous. I do not think we can simply ignore it as inconvenient. Weighing down "max" with a bunch of extra letters basically just means that people won't use it and/or can't read math with it, at which point you'd be doing everyone a favor by just removing it entirely. Every operation val oa = if (a.isNaN) None else Some(a)
val ob = if (b.isNaN) None else Some(b)
val oc = for { ra <- oa; rb <- ob } yield ra □ rb
oc.getOrElse(NaN) IEEE754 doesn't define IEEE754 does define a total ordering, and Java botches it, as do we. All the NaNs, of all types, go to the end, in random order. However, IEEE754 defines a total ordering where the negative NaNs come first, then the positive ones come after. Within the NaNs, quiet NaN is more extreme than the signaling NaNs. (It also makes -0.0 come before 0.0.) This is, unfortunately, really inconvenient because you now have to consider two different places where NaNs might appear in a sorted list. (This maintains the invariant that People like to complain about IEEE754, but if you actually think carefully through all the issues, keeping in mind the hardware and data type constraints, and that speed and correctness are both very important, it's actually really hard to do any better. The standard isn't there by accident. It is hard to use correctly. This is an intrinsic awkwardness with the reals, compounded by the very necessary constraint to use finite precision. So I think the correct thing to do is adhere to the standard whenever possible. If |
not that I'm aware of. I believe all of Scala's sorts end up deferring to |
Thanks for this. If the notion of Java did the right thing by separating the responsibilities to
|
@eed3si9n - That's a reasonable strategy that doesn't compromise anything too much. Given the heavy numeric use of But how do we get there, and what do we do in the short run to make |
|
I think a -migration warnings could be acceptable, if it is carefully
worded. Something like:
The run-time semantics of ordering has changed in 2.13 from IEEE ordering
to a total ordering. The new ordering is
the same as what Java uses. The difference affects only NaN operands,
normal values are unaffected. If you
need to maintain the old IEEE ordering you can do this by importing <name
of overriding import>
I am not sure this is all correct, but you get the idea...
…On Wed, Jan 29, 2020 at 4:58 PM Seth Tisue ***@***.***> wrote:
rules out a migration warning as completely unacceptable
Disagree — the migration warning would only appear with -Xmigration
enabled, and people are only expected to use that temporarily, when they
are in the process of upgrading. (And in practice, most people probably
don't/didn't bother.)
I take Martin's meaning to have been, "any kind of warning (with typical
settings)"
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#11844?email_source=notifications&email_token=AAGCKVWOSSDBV4S4FHVPCB3RAGRRLA5CNFSM4KCDWJNKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKHXEAI#issuecomment-579826177>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAGCKVQDW2FIUFJ337JI2SDRAGRRLANCNFSM4KCDWJNA>
.
--
Martin Odersky
EPFL and Lightbend
|
Regarding the long-term evolution of these features, I also think that the 2.13 default I rather expect to see changes in the following areas:
|
The migration warning is also hidden change in behavior, which is something we were trying to avoid. I agree on the long-term migration strategy. It's just a shame that it will take the better part of a decade to get there given our current plans. |
we did not mention when we release 2.13.2 in a month or so, I can highlight it in those notes, and I can also go back and add it to the 2.13.0 notes w/r/t migration warnings, I think we fell into a vicious cycle where we didn't use |
Consensus seems to be forming around Stefan's
I guess this would be about the only thing we could do in 2.13.x patch release. The next step then is crafting the migration warning. I personally think we should avoid mentioning Java, since I didn't understand what was meant by that exactly. What do you think of the following?
|
I'd prefer a phrasing that doesn't imply |
I think the migration message is very good. Specifically, "normal values are unaffected" is exactly right 👍 |
Martijn, I don't understand the thought behind your objection? (perhaps "ordinary numbers"?) |
We don't want to discriminate any Doubles just cause they're a bit weird. |
One can argue that But pretending that Just use "other values are not affected" instead of "normal values", and everyone should be happy. |
@SethTisue I don't think that calling them normal values makes things clearer than just listing the exceptions. For example, positive and negative infinity are handled the same. Are they normal values? Would you need to check after reading the migration warning? Something like the following side-steps that:
It IMO also vaguely suggests you are doing something wrong if you are using these abnormal values, which I thought wasn't the intent when I made the comment, but I'm not sure of anymore now that I see Martin's reply. |
The message sounds good, but actually doesn't quite convey the right thing because the So maybe, if I understand correctly how things work (which is not assured):
|
"Comparison operators" might be misleading since reader might assume that
|
Fixes scala/bug#11844 Ref scala/bug#10511 Ref scala#6410 Ref scala#76 This change the deprecation of `DeprecatedDoubleOrdering` to a migration warning instead to avoid `List(1.0, -1.0).sorted` giving deprecation warning. This also provides some documentation on the ordering instances in Scaladoc.
Here's my PR - scala/scala#8721 |
thanks for taking care of that @eed3si9n! |
Fixes scala/bug#11844 Ref scala/bug#10511 Ref scala#6410 Ref scala#76 This change the deprecation of `DeprecatedDoubleOrdering` to a migration warning instead to avoid `List(1.0, -1.0).sorted` giving deprecation warning. This also provides some documentation on the ordering instances in Scaladoc.
we need to perform the same fix for |
@SethTisue for 2.13.2? |
sure |
for reference, the previous implicit instances of |
I found that the default behavior did change between 2.12 and 2.13, so it appears to me that we do need the migration warning: 2.12.11:
2.13.2:
I'm working on a PR. |
but give a migration warning, since the default is now the strict one rather than the IEEE one we should have done this when we did the same for `Ordering`; it was just an oversight context: scala/bug#11844
PR: scala/scala#8945 |
thank you Seth 💜 |
Originally posted by @odersky in #10511 (comment)
Ref scala/scala#6323
Ref scala/scala#6410
steps
problem
It gives a deprecation warning. I think this is very bad, not to say catastrophic.
expectation
These things should just work. I don't care what ordering is used and whether it is inconsistent or not (we all know IEEE is broken and unfixable). But we should do the right thing for everyday users. As an everyday user I do not care what the ordering does for NaN and -0.0, maybe I do not even know that these weird numbers exist! (and they should not exist, if I go by common mathematical logic). But I do care that I can sort a list of doubles without going into the deeper mysteries of Scala's implicit system. This requirement is now violated.
notes
I think we should revert ASAP. We should keep the old total ordering and offer IEEE as an optional alternative that needs to be imported explicitly.
The text was updated successfully, but these errors were encountered: