Reputation: 21
I am trying to define a method using parameterised types like below..
Method Definition
def addNumber1[T](x:T):T = (x + 1.asInstanceOf[T])
But getting error as below..
And want to use it as below..
<console>:17: error: type mismatch;
found : T
required: String
def addNum[T](x:T):T = (x + 1.asInstanceOf [T])
^
addNumber1(100) // result = 101
addNumber1(100.005) // result = 101.005
addNumber1("One :") // result = "One :1"
Any help please
Upvotes: 1
Views: 90
Reputation: 369428
Let's look at this from a little different perspective: what, actually, is a parameter? What does it mean?
And let's start with something you are probably more familiar with: value parameters. What does a value parameter in a subroutine mean? Say, we have something like this:
def foo(a, b) = { /* … */ } // deliberately ignoring types and the body
It means: I have a subroutine foo
with two parameters, a
and b
. And the subroutine is general, it doesn't care what the actual concrete values of a
and b
are. It works for all values of a
and b
because it doesn't even know what the values of a
and b
are.
A little more concrete:
def plus(a: Int, b: Int) = a + b
Again, plus
doesn't know what the actual values of a
and b
are. It works for 2
and 3
just as well as for 23
and 42
or 0
and 0
. It is completely and utterly ignorant of the concrete values of a
and b
. It only knows that a
and b
exist.
Now, type parameters are the same thing, just on the type level instead of the value level.
blabla[A]
means the same thing at the type level as blabla(a)
means at the value level: "I have a thing, I have no idea what it is (and I don't care), but I'll call it A
."
Now, let's look at your method:
def addNumber1[T](x: T): T = (x + 1.asInstanceOf[T])
So, what you have effectively done, is to tell Scala that you don't know anything about T
. But then you do something with it (or rather with its instance): you add 1
to it. But, how do you even know you can add to it? You don't even know what it is! You have explicitly told the compiler: "I have some type, let's call it T
, and that is all we know about that type." So, how do you know that the type even has a +
method, if you don't know anything about it?
So, adding it cannot possibly work, it must fail!
However, the way it fails is slightly strange, and has to do with some of the pre-defined [implicit conversions]](https://scala-lang.org/files/archive/spec/2.13/07-implicits.html#views) in the scala.Predef
object. In particular, there is an implicit conversion for string concatenation, which can convert an arbitrary object into a String
and then concatenate another String
to it. In this case, it converts x
to an any2stringadd
and then tries to add 1
(or rather 1
cast as an instance of T
) to it, but the any2stringadd.+
method only takes a String
as its argument, and thus you get the strange error message that it is expecting a String
.
[Note that any2stringadd
is deprecated in Scala 2.13, so in the future you would just get an error about a non-existent method.]
There are a couple of other similar types that sometimes pop up, when you have complex type errors:
Any
, AnyRef
, AnyVal
: these types sit at the very top of Scala's type hierarchy. Sometimes, when you have some programming error, where you think you are returning the same type from two different code paths, but you actually return two different types, Scala will nonetheless try to infer the common type between the two, and end up with the only common ancestor being Any
, AnyRef
, or AnyVal
. (That would be kind of like Object
in Java or C♯.)Serializable
: this is actually the same thing as above. Lots of completely different types implement the Serializable
interface, so sometimes when you have two very different types where you actually expect the same type, Scala will helpfully infer Serializable
as the closest common ancestor, and then yell at you for doing stuff with a Serializable
that it doesn't support.Product
is a super-trait of all Product
s (i.e. Product1
, Product2
, … Product22
) and thus all Tuple
s (i.e. Tuple1
, Tuple2
, … Tuple22
). Product
is also mixed into all case class
es. So, if you have two Tuple
s of different arity or two unrelated case class
es (e.g. you sometimes return None
but then accidentally return somethingOfTypeFoo
instead of Some(somethingOfTypeFoo)
), then the most precise common type between Option
and your case class Foo
will be deduced as Product
or …Product with Serializable
: it is also possible to receive a combination of the above. E.g. Tuple
s are Serializable
, so this is actually the most precise common type of two Tuple
s of different arity.One common way to run into these problems is in a conditional expression without else
:
if (true) 42
what type does this return? Int
? No! The then
branch returns Int
, but the else
branch returns nothing (because there is no else
branch). Scala actually has a return type for not returning anything: Unit
. Unit
is a subtype of AnyVal
, Int
is a subtype of AnyVal
, so the closest common ancestor of the types of the two branches of the conditional expression is actually AnyVal
, not Int
. (Note: the fact that the else
branch is unreachable is irrelevant from a typing perspective. Reachability is a runtime thing, types are a compile time thing.)
So, how do we solve your problem? We need to tell the compiler that we actually do know a little bit about the type parameter. In particular, we know that it is some kind of "number". In Scala, there is a typeclass representing the abstract concept of "number", the scala.math.Numeric
typeclass. You can use it something like this:
def addNumber1[T : Numeric](x: T): T = implicitly[Numeric[T]].plus(x, implicitly[Numeric[T]].one)
This will work for everything that is "number-like":
addNumber1(100) //=> 101
addNumber1(100.005) //=> 101.005
You, however, want to be more general:
addNumber1("One :")
// error: could not find implicit value for evidence parameter of type Numeric[String]
String
is not an instance of the Numeric
typeclass (it is not "number-like"). There is no ready-made typeclass in Scala that fits your bill. You will have to write your own or you may look at Scalaz to see if there is something that fits your needs.
Upvotes: 2
Reputation: 51271
1st piece of advice: stay as far away from asInstanceOf[]
as possible. That's used as a way of sidestepping the type checker, which will lead to runtime errors. The type checker is your friend. Don't brush it off.
When the compiler sees the method +
on an undefined type it assumes you were trying to do some kind of String
operation. Thus the confusing error message.
In order to add a number to a parameterized type you have to tell the compiler that the type is restricted to one of the Numeric
types, and even then you are restricted in the operations that can be executed.
def addNumber1[T](x:T)(implicit ev: Numeric[T]):T = ev.plus(x, ev.one)
Note: This won't do String
concatenation but all your number test cases should pass.
Note: ev
for "evidence", i.e. if there is implicit evidence that T
is Numeric
then we can perform some math operations on it.
Upvotes: 4