1

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.

user3593261
  • 560
  • 4
  • 17

3 Answers3

3

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)))
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • This looks work somehow, why I have to use `Functor` instead of `Seq[T]`? what if I hope S to be subtype of a sequence? Functor doesn't do it, is it? – user3593261 Oct 11 '19 at 13:46
1

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

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

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

user3593261
  • 560
  • 4
  • 17