hielsnoppe
hielsnoppe

Reputation: 2869

Type erasure leads to missing parameter type for expanded function

I have written the following class that I later want to use according to the Pimp My Library pattern:

class ListX[A] (list: List[A]) {

    def zipMap[A, B, C] (that: List[B], op: (A, B) => C): List[C] =
        list.zip(that).map({
            case (a: A, b: B) => op(a, b)
        })
}

This compiles with warnings:

[warn] /src/main/scala/ListX.scala:8: abstract type pattern A is unchecked since it is eliminated by erasure
[warn]             case (a: A, b: B) => op(a, b)
[warn]                      ^
[warn] /src/main/scala/ListX.scala:8: abstract type pattern B is unchecked since it is eliminated by erasure
[warn]             case (a: A, b: B) => op(a, b)
[warn]                            ^
[warn] two warnings found

Testing it in REPL results in the following error:

scala> val a = new ListX(List(1, 1, 1))

scala> val b = List(1, 1, 1)

scala> val result = a.zipMap(b, _ + _)
error: missing parameter type for expanded function ((x$1: <error>, x$2) => x$1.$plus(x$2))

scala> val expected = List(2, 2, 2)

Since I am new to Scala I do not fully understand the warnings and the error. I know that there is this thing called "type erasure", but not how it works, and I can see that this probably leads to a missing type.

So what is wrong and how can I correct it?

Update:

Thanks to the accepted answer and its comments I managed to fix the issues and rewrote the class as follows:

implicit class ListX[A] (list: List[A]) {

    def zipMap[B, C] (that: List[B])(op: (A, B) => C): List[C]
    = list.zip(that).map({ op.tupled })
}

Upvotes: 0

Views: 459

Answers (1)

Archeg
Archeg

Reputation: 8462

You have many issues in your code, it's not only about type erasure:

In order for scala to derive the types of your lambda, you need to put it in a separate argument group: (that: List[B])(op: (A, B) => C) and then call it like zipMap(List(1, 1, 1))(_ + _). You can find lots of answers about this here. In short this is because scala does not know about type B in the group where you define it, so the first group where you've specified your type cannot infer the type

Then you have defined type parameter A twice. One in your class, and one in your method. You need it only once. Scala understands these two A as two different type parameters, so the latter A does not have any actual type, because it is not associated with list: List[A], and scala cannot derive a type for _ + _.

And you need a ClassTag context bound for your type parameters to fight the type erasure. It is written as [A : ClassTag] In short this allow scala to attach type information for both of your A and B in order to use that in scala match.

So the result code is:

(please see the code in the end of the answer as a better version, this one can be simplified)

  class ListX[A : ClassTag](list: List[A]) {

    def zipMap[B: ClassTag, C](that: List[B])(op: (A, B) => C): List[C] =
      list.zip(that).map({
        case (a: A, b: B) => op(a, b)
      })
  }

  println(new ListX[Int](List(2, 2, 2)).zipMap(List(1, 1, 1))(_ + _))

Update

After thinking a bit about @Łukasz comment I realized that I made a mistake: you really don't need a ClassTag here. The problem why it generated the warning is that you specified that your case should have type (A, B) explicitly. Instead you can give the compiler an opportunity to figure out this by itself, and if I understand what's happening here correctly - compiler can figure out that you don't need ClassTags here as they do nothing. See this:

  class ListX[A](list: List[A]) {

    def zipMap[B, C](that: List[B])(op: (A, B) => C): List[C] =
      list.zip(that).map({
        case (a, b) => op(a, b)
      })
  }

  println(new ListX[Int](List(2, 2, 2)).zipMap(List(1, 1, 1))(_ + _))

This does not produce any warnings.

Upvotes: 4

Related Questions