0
votes

After seeing the method signature of map in scala as

def map[A, B](l: List[A])(f: A => B): List[B] = ???

my layman undersanting of [A, B] in above is that they signal the method that we'll be exclusively working with generic Type A and B in the defined method.

Now I go on to implement a method to add values from two lists consquetively (basically zipWith) using similar coding pattern.

def addCorresponding[Int](l1: List[Int],
                        l2: List[Int]): List[Int] =
  (l1, l2) match {
      case (_, Nil) => Nil
      case (Nil, _) => Nil
      case (x::xs, y::ys) => {
          List((x + y)) ++ addCorresponding(xs, ys)
      }
}

but get this error while adding the elements in the last match: type mismatch found: Int Required: String

Removeing the [Int] from the mehtod sigature fixes this error.

I'm sure there are holes in my understanding of generic Types and when to add them to the method signature. I'd appreciate if someone can walk me through this and explain why the addCorresponding method with [Int] errors out but works fine without it?

1
A type parameter is basically that, a parameter. Is like when you do def plusFive(x: Int): Int = x + 5, what is x? Anything, whatever is passed to the function. It is not 1, nor 0 nor even 5 or 3, it is just a parameter. So in the first example A and B are parameters for types, meaning they are not any proper type right now, they will be filled by the compiler when called. In your second example you want both lists to be of type Int since you want to sum the elements, that is something you can only do with Ints, so not need to add a type parameter. - Luis Miguel Mejía Suárez
Also note that when you do [Int] you are creating a type parameter named Int which shadows the type Int, that is why it doesn't work when you add that. - Luis Miguel Mejía Suárez
Ah, it makes sense now. Thanks for your answer. - rghv404
There is a very similar question: stackoverflow.com/q/65322171/2359227 - Tomer Shetah
BTW, why aren't you using zip? It does exactly what you are trying to do. scastie.scala-lang.org/yYCapPvmSgWWE467QRh4ZQ - Tomer Shetah

1 Answers

1
votes

The generic method (taking a type parameter) would be

def addCorresponding[A](l1: List[A], l2: List[A]): List[A]

or, if the lists can have different types of elements

def addCorresponding[A, B, C](l1: List[A], l2: List[B]): List[C]

If you implement a method that works on a concrete type Int, then your method is no longer generic:

def addCorresponding(l1: List[Int], l2: List[Int]): List[Int]

In other words, you apply a concrete type Int here to List[_] instead of an abstract type parameter. If you write

def addCorresponding[Int](l1: List[Int], l2: List[Int]): List[Int]

then you "shadow" the concrete type Int with an abstract type parameter Int that happens to have the same name; but technically you are writing the generic method

def addCorresponding[XXX](l1: List[XXX], l2: List[XXX]): List[XXX]

If you want to keep your method generic but perform certain operations on elements of type A, you may need a type class, for example Numeric to arithmetically add elements

def addCorresponding[A](l1: List[A],
                        l2: List[A])(implicit num: Numeric[A]): List[A] =
  (l1, l2) match {
    case (_, Nil) => Nil
    case (Nil, _) => Nil
    case (x::xs, y::ys) =>
      List((num.plus(x, y))) ++ addCorresponding(xs, ys)
  }