Why does Numeric behave differently than Ordered?

2020-06-14 09:02发布

问题:

Scala has a number of traits that you can use as type classes, for example Ordered and Numeric in the package scala.math.

I can, for example, write a generic method using Ordered like this:

def f[T <% Ordered[T]](a: T, b: T) = if (a < b) a else b

I wanted to do a similar thing with Numeric, but this doesn't work:

def g[T <% Numeric[T]](a: T, b: T) = a * b

Why is there an apparent discrepancy between Ordered and Numeric?

I know there are other ways to do this, the following will work (uses a context bound):

def g[T : Numeric](a: T, b: T) = implicitly[Numeric[T]].times(a, b)

But that looks more complicated than just being able to use * to multiply two numbers. Why does the Numeric trait not include methods like *, while Ordered does include methods like <?

I know there's also Ordering which you can use in the same way as Numeric, see also this answer:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

回答1:

Ordered is just a few simple pimped methods that return either Int or Boolean, so no type-trickery is needed.

Numeric, on the other hand, has methods that return different types depending on the exact subclass used. So while Ordered is little more than a marker trait, Numeric is a fully-featured type class.

To get your operators back, you can use mkNumericOps (defined in Numeric) on the lhs operand.

UPDATE

Miles is quite right, mkNumericOps is implicit, so just importing that instance of Numeric will give you back all the magic...



回答2:

The symbolic operators are available if you import them from the implicit Numeric[T]

def g[T : Numeric](a: T, b: T) = {
  val num = implicitly[Numeric[T]]
  import num._
  a * b
}

This is clearly a bit unwieldy if you want to make use of just a single operator, but in non-trivial cases the overhead of the import isn't all that great.

Why are the operators not available without an explicit import? The usual considerations against making implicits visible by default apply here, perhaps more so because these operators are so widely used.



回答3:

You can reduce Miles' solution to only use 1 extra line by doing this:

Add an implicit conversion from A : Numeric to Numeric[A]#Ops

object Ops {
  implicit def numeric[A : Numeric](a: A) = implicitly[Numeric[A]].mkNumericOps(a)
}

Then bring this into scope in your method

def g[T : Numeric](a: T, b: T) = {
  import Ops.numeric
  a * b
}

See Scala ticket 3538 for more info.