Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I define a generic function in scala that accepts all 'computable' Datatypes?

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?

like image 526
Michael W. Avatar asked Jan 21 '26 10:01

Michael W.


2 Answers

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

like image 80
Guru Stron Avatar answered Jan 24 '26 05:01

Guru Stron


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)
like image 37
Dima Avatar answered Jan 24 '26 03:01

Dima



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!