Reputation: 107
I was playing around with the following piece of code:
class A
class B
class C
trait Codecs[L] {
case class Codec[R](val code: L => R, val decode: R => L)
object Codec
def code[R](foo: L)(implicit codec: Codec[R]): R = codec.code(foo)
def decode[R](bar: R)(implicit codec: Codec[R]): L = codec.decode(bar)
}
object Codecs {
implicit object ACodecs extends Codecs[A] {
object Codec {
implicit val ab: Codec[B] = new Codec(_ => new B, _ => new A)
implicit val ac: Codec[C] = new Codec(_ => new C, _ => new A)
}
}
}
object test extends App {
val codecs = implicitly[Codecs[A]]
codecs.code[B](new A)
}
It won't compile, as the compiler is unable to find an implicit value of type Codecs.Codec[B]
.
As I understand, the two values ab
and ac
are of type Acodecs.Codec[_]
(or something like that), which isn't exactly what the compiler is looking for. I am also aware that moving the case class Codec[_]
and its companion outside of the trait solves the problem (after making it take 2 type params). If an implicit value is required, the compiler should include the companion object of the required type in the implicit scope. My questions are:
trait
(ideally alter the type signature of the implicit param) to make this compile? How would one refer to the type Acodecs.Codec[_]
from inside the trait Codecs[_]
?Like, how do you do this typeclass thing on a nested type?
Is there like a pattern or something dealing with this sort of problem?
Upvotes: 3
Views: 427
Reputation: 18424
The issue is that your type is bound to a specific instance since it's an inner class. And the compiler doesn't know that implicitly[Codecs[A]]
is giving the exact same instance as what it's finding implicitly on the next line. For instance, if you pass it explicitly:
codecs.code[B](new A)(Codecs.ACodecs.Codec.ab)
You get this error message:
type mismatch;
found : Codecs.ACodecs.Codec[B]
required: codecs.Codec[B]
So it believes the enclosing instances to be possibly different, and so different types.
I've never really seen this specific kind of nesting of implicits - i.e. an implicit typeclass with path-dependent implicit typeclasses within it. So I doubt there's a pattern for dealing with it and would in fact kind of recommend against it. It seems overly complicated. Here's how I would personally treat this case:
case class Codec[L, R](val code: L => R, val decode: R => L)
trait Codecs[L] {
type LocalCodec[R] = Codec[L, R]
def code[R](foo: L)(implicit codec: LocalCodec[R]): R = codec.code(foo)
def decode[R](bar: R)(implicit codec: LocalCodec[R]): L = codec.decode(bar)
}
object Codecs {
implicit object ACodecs extends Codecs[A] {
implicit val ab: LocalCodec[B] = new LocalCodec(_ => new B, _ => new A)
implicit val ac: LocalCodec[C] = new LocalCodec(_ => new C, _ => new A)
}
}
object test extends App {
import Codecs.ACodecs._
val codecs = implicitly[Codecs[A]]
codecs.code[B](new A)
}
You still get the benefit of a "half-narrowed" type to work with but it's just a type alias so there's no path-dependency issues.
Upvotes: 2