PrasadDonda
PrasadDonda

Reputation: 21

Scala Beginner trying to use Parameterised Type

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

Answers (2)

J&#246;rg W Mittag
J&#246;rg W Mittag

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 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.)

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

jwvh
jwvh

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

Related Questions