I am trying to understand the type parameter concepts in scala.
def sum [A] (a:A):A={a}
// used single parameter and its working fine, able to pass any data type.
Here:
def sum[A](a:A,b:A):A={a+b} //declare two arguments
<console>:7: error: type mismatch;
found : A
required: String
def sum[A](a:A,b:A):A={a+b}
The above function has a type parameter, I have not mentioned any data type - then how come it's treated as string?
def sum[A](a:A,b:A):A={a+b}
You are adding T
+
T
, the compiler can't infer the +
for T
, it will use the default implicit +
: any2stringadd
, so the compiler will throw required: String
error.
see https://issues.scala-lang.org/browse/SI-8229
implicit final class any2stringadd[A](private val self: A) extends AnyVal {
def +(other: String): String = String.valueOf(self) + other
}
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
."
So, what you have effectively done, is to tell Scala that you don't know anything about A
. But then you do something with it (or rather with its instance): you add it. But, how do you even know you can add it? You don't even know what it is!
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 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 a
to an any2stringadd
and then tries to add b
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.)