Reputation: 568
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
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
Reputation: 170745
There are two problems:
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.
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
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