Currently (Scala 2.12.4 and 2.13.0-M3), there are some problems with Ordering and Numeric, particularly with regards to Float and Double (not BigDecimal).
The current default Orderings for Float and Double are internally inconsistent (see scala/bug#10511) with compare behaving the same as j.l.{Float,Double}.compare, the comparison methods (lt, gteq, equiv, etc.) being consistent with IEEE spec, and extrema methods (min and max) behaving the same as math.min and math.max (which is mostly consistent with IEEE spec). For Orderings, it is confusing for many methods to be inconsistent with a total ordering.
The default Numerics for Float and Double inherit from the default Orderings for them. For Numeric, it is absolutely expected that their behaviour conforms to IEEE spec as much as possible (which is why their behaviour was changed to the way it is in the first place). In addition, Ordering.min(x, y) behaves the same as x min y for Float and Double (violating this is extremely unexpected). However, this still leaves the Ordering internally inconsistent, as it is impossible to implement Ordering.compare in a way that complies with IEEE spec.
The root problem is that IEEE floating point values do not have a total ordering; only a partial ordering. This leaves a few solutions:
Leave Ordering for Float and Double as is; it is mostly compliant with IEEE spec, but internally inconsistent.
Revert the behaviour of the default Orderings for Float and Double to be consistent with their compare implementations (and a total ordering), while no longer being consistent with IEEE spec. At the same time, the old (current) Orderings can be used for the default implementations of their respective Numerics; the behaviour of Numeric[Float] and Numeric[Double] will remain as is.
Change Numeric to inherit from PartialOrdering instead of Ordering. Ordering[Float] and Ordering[Double] are changed to be consistent with a total ordering, and Numeric[Float] and Numeric[Double] are defined with PartialOrderings consistent with IEEE spec. There may additionally be an Extrema typeclass which Numeric extends, which defines min and max methods (which would be consistent with math.min and math.max). A deprecated implicit conversion from Numeric to Ordering can perhaps be added to reduce breakage.
Something else?
See scala/scala#6323 for a longer discussion of the issues here. A huge thanks to @Ichoran for the discussion and great points raised there.
To me at least, changing Numeric to inherit from PartialOrdering is the most âcorrectâ, and likely to be least confusing in the long term (despite causing breakage in the immediate future).
When considering changing Numeric to inherit from PartialOrdering instead of Ordering, I considered what methods make sense for a Numeric.
All the comparison methods make sense. But reverse doesnât. Itâs perfectly sensible to reverse an Ordering or PartialOrdering - it means that the less-than and greater-than relationships get reversed. What does it mean to reverse a Numeric though? That doesnât make any sense.
Perhaps, instead of inheriting from Ordering (or PartialOrdering; whatever the case may be), it should contain an Ordering. What is a Numeric? An object defining the behaviour of a class of numbers? Is the behaviour of a type of number itself an ordering for that type, or should it just have that ordering? To me, the latter seems more natural. It doesnât make sense to reverse a Numeric, but it makes sense to get the Ordering of a Numeric type, and reverse that.
Perhaps we should change option â3â above to be:
Do all Numerics have even a partial ordering though?
Take complex numbers. Surely, complex numbers are numbers; therefore they ought to be able to have a Numeric. But complex numbers donât have any meaningful order. Is 6 + 3i greater than 4 + 7i, or even 3 - 2i? You could define a PartialOrdering that, when either the real or imaginary parts are equal, compares the other part of the two numbers, but would that be useful or meaningful? The vast majority of numbers would still not be comparable, and itâs not clear that such an ordering is anything but arbitrary.
If you are talking about natural ordering, then possibly no. But the question is what you call numeric and whether do you like an idea of having several type classes for a single type (instead of having, for instance, tagged types with a single type class).
It depends. scala.math.Numeric is not only just a number, but a thing that can be converted to Double, Float, Int and Long. Maybe, the name is not really the best.
If we were considering a number to be only those that contain all operations of fields, then current Numeric type class should have be splitted to two: one with field-like operations, one with convertions. Iâm sure, it is done cleanly in libraries like scalaz.
Welcome to Scala 2.12.4 (OpenJDK 64-Bit Server VM, Java 1.8.0_151).Type in
expressions for evaluation. Or try :help.
scala> Double.NaN.longValue
res0: Long = 0
I am not sure it is possible to guess what people are thinking when they say something like xs.sorted, and it is surely a problem if people are thinking IEEE rules and get a total order, or vice versa. To me this suggests that
Numeric can still extend Ordering, so long as
There is an Ordering which is total that collides.
Then people would have to manually disambiguate the two. I think we can do this by having not just Ordering[Double] in scope but a RealOrdering[Double]. Then people have to manually override the ordering, and the error message can probably tell them what to plug in. Or docs can refer to Double.totalOrder vs Double.ieeeOrder.
This seems like the simplest solution. It still has a few sharp edges, but itâs not clear to me that anything else would be better.
Ideally, I think Numeric and family should be composed of Orderings, rather than extending them, as several methods (reverse, on, orElse, etc.) donât make sense for Numeric; however, after some experimentation, it is too late to make that change. It breaks most code that uses Numeric.
How do you get both implicits in scope, and customize the message for ambiguous implicits? Or do you just hope they see the two of them, and check their documentation?
Personally I think that in âideal worldâ Numeric should not either extend or contain Ordering just because they are about different things. I think that if some method needs both number operations (like addition and multiplication) and ordering, if should be declared like
def f[T:Numeric:Ordering](ss: Seq[T]) = // e.g. multiply the smallest and the biggest number in the sequence and add the median.
This allows to use e.g. Doubleâs operations to be used with non-standard ordering. Now, if you need to change the ordering for a method declared like
def g[T:Numeric](ss: Seq[T]) = // whatever using both ordering and numeric operations.
you need to declare another Numeric with the same numeric operations but different ordering operations. This seems to be weird, however depends on whether you accept an idea of having several typeclasses for a single type.
You raise a good point about typeclass composition. IMHO, inheritance is usually the wrong way to compose them. We donât really want to be stating that we have something that is at the same time an ordering over a numeric type and integral field operators over that numeric type. We want some way to conveniently bundle up the fact that both typeclasses should be available in implicit scope, and that those two typeclasses promise to obey some mutual consistency laws.
This is a general issue with how typeclass latices are modelled in scala. The approach of modelling the latice through a trait inheritance latice doesnât serve us well IMHO. However, I donât have an obvious fix.