В вашем коде есть несколько ошибок.
Во-первых, если вы вернете (k1, k2)
, тогда k1
, k2
должно быть the[Identifiable[K1]].identify(id._1)
, the[Identifiable[K2]].identify(id._2)
соответственно и не наоборот, как вы их определили.
Во-вторых, вы забыли уточнение типа. Вы объявляете тип возврата identifiableTuple
как Identifiable[(K1,K2)]
вместо правильного Identifiable[(K1,K2)] { type K = (a.K, b.K)}
(он же Identifiable.Aux[(K1,K2), (a.K, b.K)]
). Если вы сохраняете Identifiable[(K1,K2)]
, вы на самом деле вытесняете правую сторону
new Identifiable[(K1,K2)]{
...
type K = (a.K, b.K)
...
}
и информацию, которая для этого неявного экземпляра type K = (a.K, b.K)
будет потеряна.
Поскольку вам нужно восстановить уточнение типа, вы можете не пишите identifiableTuple
с контекстными границами, вы должны написать его неявным блоком
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)
}
}
Вы можете проверить свой код во время компиляции
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)}]
Вы можете переписать это с помощью 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)
}
} // (*)
и
implicit val int: Identifiable.Aux[Int, Double] = null
implicit val str: Identifiable.Aux[String, Char] = null
implicitly[Identifiable.Aux[(Int, String), (Double, Char)]]
Это похоже на ответ @ MateuszKubuszok
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)
}
} // (**)
, хотя последний нуждается в дополнительном определении двух типов параметров.
И в-третьих, вы не можете написать (*) с implicitly
или даже the
внутри, как
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)
}
}
Дело в том, что путь зависит от типы определены в Scala, так что даже когда a == a1
, b == b1
типы a.K
и a1.K
, b.K
и b1.K
различны (a1
, b1
равны the[Identifiable[K1]]
, the[Identifiable[K2]]
). Таким образом, вы возвращаете (k1, k2)
неправильного типа (a1.K,b1.K)
.
Но если вы напишите это в (**) стиле
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)
}
}
, тогда все будет хорошо (с the
, но не с implicitly
), потому что компилятор делает вывод, что (k1,k2)
имеет тип (K1,K2)
.