I've had this problem a couple of times now:
I have a function that computes something, lets say
def square(n: Int): Int = n * n
(Very simple example, but this will do)
Then I have the same 'algorithm' for another datatype, lets say long:
def square(n: Long): Long = n * n
And then for BigInt, Short, Byte and so on.
If my algorithm is more complex and longer than in this example, I have a lot of code repetition.
What I would like to have is a generic definition like:
def square[T :> AnyVal](n: T): T = n * n
But this does not work, because in the class hirachy, below AnyVal with Int and Long and Float there also is Boolean and Unit. And for Boolean and Unit the term n * n does not make sense and I get a compiler error (correctly).
I only want my function to work for the 'computable' Datatypes like Int, Long, Float, ... that have all the usual math operators like +, *, /, < and so on and then formulate my algorithm or calculation with this operators for all at once.
Of course I can match on the functions input variable n, and then handle each case differently, but there I also will repeat all the code like before with overloading.
I tried to make my own trait 'Computables' and then extend to the other classes Int, Long, ..., but the compiler complains '... cannot extend final class Int'
Is this even possible? Am I missing something?
You can use the Numeric trait:
def square[T](n: T)(using numeric: Numeric[T]): T = numeric.times(n,n)
or
def square[T](n: T)(using numeric: Numeric[T]): T = {
import numeric._
n * n
}
Demo @scastie
I think, you are looking for a type class
The Numeric mentioned in the other answer is an example of a type class describing all numeric types. But you can generalize that to describe any kind of behavior.
Check out the link I mentioned above, it has the details and examples, but at a high level, the idea is this:
def someFunction[T : SomeType](t: T) is equivalent to def someFunction[T](t: T)(implicit ev: SomeType[T])
This means that something like
val foo: Foo = ???
someFunction(foo)
will compile if and only if, you have an implicit instance of type SomeType[Foo] somewhere in scope.
So, that solves the first part of your problem: we have Numeric instances defined for all the "numeric" types, but not for strings or boolean, so this way you are restricting the types that can be sent to your function to a specific "class" (thus "type class") of types.
The other purpose of the type class is to express the common behaviors of the types it includes. You can "summon" the implicit "evidence" of the type class to access it:
def someFunction[T : SomeType](t: T) = implicitly[SomeType[T]].doStuff(t)
In your case, with the square:
def square[T : Numeric](t: T) = implicitly[Numeric[T]].times(t, t)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With