Reputation: 1193
I was trying to create a Generic Calculator in Scala which can work on all Integer types. When I am trying the following code it gives me an error
Error: "Type mismatch. Required: String, found: T"
class Calculator[T <: Integral[T]] {
def add[T](a: T, b: T): T = a + a
}
Can someone please explain to me how come it was expecting String type?
Upvotes: 0
Views: 263
Reputation: 5073
The reason why you see
Error: "Type mismatch. Required: String, found: T"
is that by default Scala 2 defines the following implicit method in Predef
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
(this would not compile in Scala 3). If you wanted to write single non-generic calculator class that would work for all integral types (whatever that means for you - Scala doesn't have inheritance hierarchy that would restrict types to just Int
, Byte
, Long
). You would use your own Integer
typeclass
class Calculator {
def add[A: Integer](lhs: A, rhs: A): A = implicitly[Integer[A]].plus(lhs, rhs)
}
with a following definition
trait Integer[A] {
def plus(lhs: A, rhs: A): A
}
object Integer {
implicit val Int: Integer[Int] = new Integer[Int] {
override def plus(lhs: Int, rhs: Int): Int = lhs + rhs
}
// note explicit toByte cast -> byte addition can overflow!
// (thus result type is Int and you might want to stick
// to the original type in your use case)
implicit val Byte: Integer[Byte] = new Integer[Byte] {
override def plus(lhs: Byte, rhs: Byte): Byte = (lhs + rhs).toByte
}
}
To make it look a bit nicer, you might leverage extension methods.
class Calculator {
import Integer._
def add[A: Integer](lhs: A, rhs: A): A = lhs + rhs
}
object Integer {
implicit class IntegerOps[A: Integer](lhs: A) {
def +(rhs: A): A = implicitly[Integer[A]].plus(lhs, rhs)
}
// the rest of the object stays the same
}
and with Scala 3, you can write it even more concisely
class Calculator {
def add[A: Integer](lhs: A, rhs: A): A = lhs + rhs
}
trait Integer[A]:
extension(a: A) def +(b: A): A
given Integer[Int] with
extension(a: Int) def +(b: Int): Int = a + b
given Integer[Byte] with
extension(a: Byte) def +(b: Byte): Byte = (a + b).toByte
Edit: per @Tim suggestion you can use Integral
type class that is predefined. I will still leave my answer as a reference in case you wanted to implement similar typeclass by yourself.
Upvotes: 1
Reputation: 27356
To answer the specific question, the type of a + b
is String
because Scala has a default +
that works on any object by converting both arguments to a String
and concatenating them. But what this is saying is that there is no other method +
on a
that can be used.
The broader problem is that Numeric
is not the base class of all numeric types, but rather a typeclass that defines numeric behaviour on a type T
. So the class defintion should look like this:
class Calculator[T : Integral] {
This tells the compiler that you can't create an instance of Calculator[T]
unless there is an implicit instance of Integral[T]
visible to the compiler at the time.
So there is an instance of Integral[T]
that has methods that operate on T
to do the basic numeric operations (plus
, times
, negate
etc.). The next question is, how do I get hold of this instance of Integral[T]
? The answer to that is implicitly
, which will return the typeclass value for a type as long as it is available (otherwise it is a compile time error).
So the expression implicitly[Integral[T]]
will return an instance of Integral[T]
for whatever type was used to create the Calculator
. And that instance knows how to plus
values of type T
. Once you know this, you can write your add
function:
class Calculator[T : Integral] {
def add(a: T, b: T): T = implicitly[Integral[T]].plus(a, b)
}
[Also note that there is no type parameter on add
because the type parameter is on the class, so it is not def add[T](...)
but just def add(...)
]
Upvotes: 3