I\'m trying to derive a tuple instance for a type class with dependent type. I\'m using shapeless to create summon the type class for the tuple elements. I\'m having trouble mat
There are several mistakes in your code.
Firstly, if you return (Typo is fixed.)(k1, k2)
then k1
, k2
should be the[Identifiable[K1]].identify(id._1)
, the[Identifiable[K2]].identify(id._2)
correspondingly and not vice versa as you defined them.
Secondly, you forgot type refinement. You declare return type of identifiableTuple
to be Identifiable[(K1,K2)]
instead of correct Identifiable[(K1,K2)] { type K = (a.K, b.K)}
(aka Identifiable.Aux[(K1,K2), (a.K, b.K)]
). If you keep Identifiable[(K1,K2)]
you actually upcast right hand side
new Identifiable[(K1,K2)]{
...
type K = (a.K, b.K)
...
}
and information that for this implicit instance type K = (a.K, b.K)
will be lost.
Since you have to restore type refinement you can't write identifiableTuple
with context bounds, you have to write it with implicit block
implicit def identifiableTuple[K1, K2](implicit
a: Identifiable[K1],
b: Identifiable[K2]
): Identifiable[(K1, K2)] {type K = (a.K, b.K)} = new Identifiable[(K1, K2)] {
type K = (a.K, b.K)
override def identify(id: (K1, K2)): K = {
val k1 = a.identify(id._1)
val k2 = b.identify(id._2)
(k1, k2)
}
}
You can test your code at compile time
implicit val int: Identifiable[Int] { type K = Double } = null
implicit val str: Identifiable[String] { type K = Char } = null
implicitly[Identifiable[(Int, String)] { type K = (Double, Char)}]
You can rewrite this with Aux
pattern type Aux[M, K0] = Identifiable[M] { type K = K0 }
implicit def identifiableTuple[K1, K2](implicit
a: Identifiable[K1],
b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
type K = (a.K, b.K)
override def identify(id: (K1, K2)): K = {
val k1 = a.identify(id._1)
val k2 = b.identify(id._2)
(k1, k2)
}
} // (*)
and
implicit val int: Identifiable.Aux[Int, Double] = null
implicit val str: Identifiable.Aux[String, Char] = null
implicitly[Identifiable.Aux[(Int, String), (Double, Char)]]
This is similar to @MateuszKubuszok's answer
implicit def identifiableTuple[M1, M2, K1, K2](implicit
a: Identifiable.Aux[M1, K1],
b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
type K = (K1, K2)
override def identify(id: (M1, M2)): K = {
val k1 = a.identify(id._1)
val k2 = b.identify(id._2)
(k1, k2)
}
} // (**)
although the latter needs extra inferrence of two type parameters.
And thirdly, you can't write (*) with implicitly
or even the
inside like
implicit def identifiableTuple[K1, K2](implicit
a: Identifiable[K1],
b: Identifiable[K2]
): Identifiable.Aux[(K1, K2), (a.K, b.K)] = new Identifiable[(K1, K2)] {
type K = (a.K, b.K)
override def identify(id: (K1, K2)): K = {
val k1 = the[Identifiable[K1]].identify(id._1)
val k2 = the[Identifiable[K2]].identify(id._2)
(k1, k2)
}
}
The thing is that path-dependent types are defined in Scala so that even when a == a1
, b == b1
types a.K
and a1.K
, b.K
and b1.K
are different (a1
, b1
are the[Identifiable[K1]]
, the[Identifiable[K2]]
). So you return (k1, k2)
of wrong type (a1.K,b1.K)
.
But if you write it in (**) style
implicit def identifiableTuple[M1, M2, K1, K2](implicit
a: Identifiable.Aux[M1, K1],
b: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K2)] = new Identifiable[(M1, M2)] {
type K = (K1, K2)
override def identify(id: (M1, M2)): K = {
val k1 = the[Identifiable[M1]].identify(id._1)
val k2 = the[Identifiable[M2]].identify(id._2)
(k1, k2)
}
}
then it will be ok (with the
but not with implicitly
) because compiler infers that (k1,k2)
has type (K1,K2)
.