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
Try Aux pattern
trait Identifiable[M] {
type K
def identify(id: M): K
}
object Identifiable {
type Aux[M, K0] = Identifiable[M] { type K = K0 }
implicit def identifiableTuple[M1, K1, M2, K2](
implicit
identifiable1: Identifiable.Aux[M1, K1],
identifiable2: Identifiable.Aux[M2, K2]
): Identifiable.Aux[(M1, M2), (K1, K1)] = new Identifiable[(M1, M2)] {
type K = (K1, K2)
def identify(id: (M1, M2)): (K1, K2) =
identifiable1.identify(id._1) -> identifiable2.identify(id._2)
}
}
Aux pattern was invented because
So just use Aux to derive things.
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).