user3593261
user3593261

Reputation: 568

type is erased from implicit class

I'm having a implicit class to add a certain function for a case class, for example:

case class TestClass(name:String)

implicit class ChangeProduct[S <: Seq[T], T <: Product](seq: Option[Seq[T]]) {
    def convert(expr: T => T): Option[Seq[T]] = seq.map(_.map(expr))
}

val c = Option(List(TestClass("a"), TestClass("b")))
val r = c.convert(p => p.copy(name = p.name.toUpperCase()))
println(r)

I'm happy to see the output is

Some(List(TestClass(A), TestClass(B)))

But now I try to make the implicit class more generic by change its parameter to seq:Option[S]:

implicit class ChangeProduct[S <: Seq[T], T <: Product](seq: Option[S]) {
    def convert(expr: T => T): Option[S] = seq.map(_.map(expr))
}

val c = Option(List(TestClass("a"), TestClass("b")))
val r = c.convert(p => p.copy(name = p.name.toUpperCase()))
println(r)

Unfortunately I got error message:

Error:(37, 51) type mismatch;
   found   : Seq[T]
   required: S
        def convert(expr: T => T): Option[S] = seq.map(_.map(expr))

And for expression p.copy(name = p.name.toUpperCase()), it said

Type mismatch.
    Required: Nothing => Nothing
    Found : Nothing => Any

I think it might be type erasure problem, but I don't know how to fix it.

Upvotes: 1

Views: 81

Answers (3)

user3593261
user3593261

Reputation: 568

All right, I figured it out. If I do want limit the S be sub type of Seq, I should give it an explicit parameter instead of underscore. And also because I have T <: Product, the U must be covariant:

implicit class ChangeProduct[S[+U] <: Seq[U], T <: Product](seq: Option[S[T]]) {
    def convert(expr: T => T): Option[Seq[T]] = seq.map(_.map(expr))
}

val c = Option(List(TestClass("a"), TestClass("b")))
val r = c.convert(p => p.copy(name = p.name.toUpperCase()))
println(r)

Thanks @Mario Galic and @Alexey Romanov

Upvotes: 0

Alexey Romanov
Alexey Romanov

Reputation: 170745

There are two problems:

  1. The reason for the first error message is that map on S doesn't have to return S. It usually does, but it isn't guaranteed, and certainly not encoded in the types.

  2. The reason for Nothing is that unfortunately Scala's type inference can't handle inferring T and S together in this situation.

You can fix both problems (in 2.13) by using IterableOps as the bound instead:

implicit class ChangeProduct[S[A] <: IterableOps[A, S, S[A]], T <: Product](seq: Option[S[T]]) {
    def convert(expr: T => T): Option[S[T]] = seq.map(_.map(expr))
}

(the Product bound doesn't seem to be useful here).

Upvotes: 1

Mario Galic
Mario Galic

Reputation: 48420

The problem is not type erasure but we need to use type constructor S[_] instead of just S. Consider Functor constraint

S[_]: Functor

like so

import cats._
import cats.implicits._

case class TestClass(name:String)

implicit class ChangeProduct[S[_]: Functor, T](s: Option[S[T]]) {
  def convert(expr: T => T): Option[S[T]] = s.map(_.map(expr))
}

val c = Option(List(TestClass("a"), TestClass("b")))
c.convert(p => p.copy(name = p.name.toUpperCase()))

which outputs

res0: Option[List[TestClass]] = Some(List(TestClass(A), TestClass(B)))

Upvotes: 3

Related Questions