Reputation: 5875
I am trying to construct a type hierarchy for numerical domain types. e.g. a Year
is an Int
(which is a Number
), a Percentage
is a Double
, which is a Number
, etc. I need the hierarchy so that I can call toInt
or toDouble
on the values.
However, the Scala type hierarchy for the primitive numeric types has no common ancestor except AnyVal
. This does not contain the to{Int, Double}
functions that I need.
The closest type I could find is Numeric[T]
, which seems to exist primarily for some compiler trickery.
In Java, all the numbers derived from Number
(including the arbitrary precision ones). How does one define an interface that caters for numerical types of object in Scala?
I'm currently hacking it with duck typing:
Any {
def toInt: Int
def toDouble: Double
}
which is not only long-winded, but incurs runtime reflection costs. Is there anything better?
Upvotes: 35
Views: 17117
Reputation: 2230
the answer from @gzm0 is a static solution, which the type must be checked in compiling time, I give a dynamic solution which cast the type in runtime,
def toDoubleDynamic(x: Any) = x match {
case s: String => s.toDouble
case jn: java.lang.Number => jn.doubleValue()
case _ => throw new ClassCastException("cannot cast to double")
}
It use case match to choose the correct type in runtime.
Upvotes: -2
Reputation: 14842
Numeric[T]
is exactly what you are looking for. Scala's way to go here is type classes (i.e. a thing like Numeric
).
Instead of
def foo(x: java.lang.Number) = x.doubleValue
write one of
def foo[T](x: T)(implicit n: Numeric[T]) = n.toDouble(x)
def foo[T : Numeric](x: T) = implicitly[Numeric[T]].toDouble(x)
where the second is (almost) nothing but syntactic sugar.
Writing calls to the instance of Numeric
every time you need an operation can become clumsy when the expression is more complex. To mitigate this, Numeric
provides the implicit conversion mkNumericOps
which augment T
with the common ways of writing mathematical operations (i.e. 1 + 2
rather than n.plus(1,2)
).
In order to use those, just import the members of the implicit Numeric
:
def foo[T](x: T)(implicit n: Numeric[T]) = {
import n._
x.toDouble
}
Note that due to restrictions on import
the abbreviated syntax for the implicit is hardly desirable here.
What happens here? If an argument list is marked as implicit
, the compiler will automatically put a value of the required type there iff exactly one value of that type that is marked as implicit
exists in scope. If you write
foo(1.0)
The compiler will automatically change this to
foo(1.0)(Numeric.DoubleIsFractional)
providing the method foo
with operations on Double
.
The huge advantage of this is that you can make types Numeric
without them knowing. Suppose you have a library that gives you a type MyBigInt
. Now suppose that in the Java world - unfortunately - the developers did not make it extend Number
. There is nothing you can do.
In Scala, you can just write
implicit object MyBigIntIsNumeric extends Numeric[MyBigInt] {
def compare(x: MyBigInt, y: MyBigInt) = ...
// ...
}
and all your code using Numeric
will now work with MyBigInt
but you did not have to change the library. So Numeric
could even be private to your project and this pattern would still work.
Upvotes: 68