Type parameter in scala

2020-02-06 18:14发布

问题:

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?

回答1:

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
  }


回答2:

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 Products (i.e. Product1, Product2, … Product22) and thus all Tuples (i.e. Tuple1, Tuple2, … Tuple22). Product is also mixed into all case classes. So, if you have two Tuples of different arity or two unrelated case classes (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. Tuples are Serializable, so this is actually the most precise common type of two Tuples 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.)



标签: scala