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?
CodePudding user response:
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(...)]
CodePudding user response:
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
