J'ai une méthode compare
personnalisée qui prend deux paramètres. On s'attend à ce que l'un d'eux soit implicitement convertible en un autre:
error: ambiguous reference to overloaded definition, both method compare in object Test of type [T1, T2](a: T1, b: T2)(implicit ev: T2 => T1)Boolean and method compare in object Test of type [T1, T2](a: T1, b: T2)(implicit ev: T1 => T2)Boolean match argument types (Test.Foo,Test.Bar) and expected result type Any implicit def foo2bar(f: Foo): Bar = Bar(f.s)
Cependant, cet extrait me donne une erreur:
object Test extends App { def compare[T1, T2](a: T1, b: T2)(implicit ev: T1 => T2) = compareImpl[T2](ev(a), b) def compare[T1, T2](a: T1, b: T2)(implicit ev: T2 => T1) = compareImpl[T1](a, ev(b)) def compareImpl[T](a: T, b: T) = a == b case class Foo(s: String) case class Bar(s: String) implicit def foo2bar(f: Foo): Bar = Bar(f.s) println(compare(Foo("hello"), Bar("hello"))) }
Si je supprime le second compare
, cela fonctionne, mais si je fais compare (Bar ("bonjour), Foo (" bonjour "))
il ne se compilera pas.
Comment puis-je avoir ces deux versions sans ambiguïté?
3 Réponses :
Le problème ici est que vos deux fonctions compare
ont exactement le même paramètre de type, ce qui est ambigu pour que le compilateur Scala détermine lequel utiliser.
Par exemple, lorsque vous effectuez une comparaison de compare [Foo, Bar]
, il n'est pas clair pour le compilateur Scala s'il doit utiliser la fonction compare
avec ( ev implicite: T1 => T2)
ou le second avec (implicit ev: T2 => T1)
, car à la fois Foo
et Bar
peut être placé comme T1
ou T2
.
En fait, c'est la raison pour laquelle lorsque vous supprimez l'une des fonctions de comparaison, cela fonctionne. Parce qu'il n'y a pas de version surchargée de la fonction compare
et que Foo
et Bar
peuvent être placés comme T1
et T2
dans votre seule et unique fonction compare
.
Voici une réponse à une autre question de Stackoverflow qui est en quelque sorte liée à votre problème et qui décrit le problème en détails:
C'est en fait clair car il n'y a que Foo => Bar
et pas de Bar => Foo
. Le compilateur n'est pas en mesure de les distinguer correctement. Il ne doit être ambigu que si la preuve de conversion est bidirectionnelle.
Oui, c'est clair pour nous mais pas pour le compilateur Scala. Et c'est la raison pour laquelle lorsque vous supprimez l'une des fonctions compare
, cela fonctionnera réellement. Le fait qui rendait cela ambigu pour le compilateur Scala est que (implicit ev: T1 => T2)
et (implicit ev: T2 => T1)
peuvent pointer vers le même fonction foo2bar
qui est ambiguë pour le compilateur Scala.
J'ai fini par utiliser une macro car actuellement Scala n'a pas de type lambda et il effectue un effacement de type générique, donc rien de tel ne sera pris en charge dès la sortie de la boîte.
Définition de macro:
case class Foo(s: String) case class Bar(s: String) implicit def foo2bar(f: Foo): Bar = Bar(f.s) println(compare(Foo("hello"), Bar("hello"))) println(compare(Bar("hello"), Foo("hello")))
Solution sans macros (elle est basée sur des classes de types)
def compare[T1, T2](a: T1, b: T2)(implicit cmp: Compare[T1, T2]) = (compareImpl[cmp.T] _).tupled(cmp(a, b)) def compareImpl[T](a: T, b: T) = a == b trait Compare[T1, T2] { type T type Out = (T, T) def apply(a: T1, b: T2): Out } trait LowPriorityCompare { type Aux[T1, T2, T0] = Compare[T1, T2] { type T = T0 } def instance[T1, T2, T0](f: (T1, T2) => (T0, T0)): Aux[T1, T2, T0] = new Compare[T1, T2] { override type T = T0 override def apply(a: T1, b: T2): Out = f(a, b) } implicit def reverseCompare[T1, T2](implicit ev: T2 => T1): Aux[T1, T2, T1] = instance((a, b) => (a, ev(b))) } object Compare extends LowPriorityCompare { implicit def directCompare[T1, T2](implicit ev: T1 => T2): Aux[T1, T2, T2] = instance((a, b) => (ev(a), b)) } case class Foo(s: String) case class Bar(s: String) implicit def foo2bar(f: Foo): Bar = Bar(f.s) implicit def bar2foo(f: Bar): Foo = Foo(f.s) println(compare(Foo("hello"), Bar("hello"))) // true
Ou vous pouvez même prioriser les directions directe et inverse si vous le souhaitez
def compare[T1, T2](a: T1, b: T2)(implicit cmp: Compare[T1, T2]) = (compareImpl[cmp.T] _).tupled(cmp(a, b)) def compareImpl[T](a: T, b: T) = a == b trait Compare[T1, T2] { type T type Out = (T, T) def apply(a: T1, b: T2): Out } object Compare { type Aux[T1, T2, T0] = Compare[T1, T2] { type T = T0 } def instance[T1, T2, T0](f: (T1, T2) => (T0, T0)): Aux[T1, T2, T0] = new Compare[T1, T2] { override type T = T0 override def apply(a: T1, b: T2): Out = f(a, b) } implicit def directCompare[T1, T2](implicit ev: T1 => T2): Aux[T1, T2, T2] = instance((a, b) => (ev(a), b)) implicit def reverseCompare[T1, T2](implicit ev: T2 => T1): Aux[T1, T2, T1] = instance((a, b) => (a, ev(b))) } case class Foo(s: String) case class Bar(s: String) implicit def foo2bar(f: Foo): Bar = Bar(f.s) println(compare(Foo("hello"), Bar("hello"))) // true
p>
Belle solution! Une question - lorsque le compilateur recherche Aux implicites, il peut correspondre à directCompare ou reverseCompare car ils ont le même type d'effacement. Comment ne crée-t-il pas d'ambiguïté?
@texasbruce Les implicits sont résolus pendant la phase typer
et l'effacement de type se produit pendant la phase effacement
typelevel.org/scala/docs/phases.html Ainsi, lors de la résolution implicite, aucun type n'est encore effacé. L'effacement de type n'est tout simplement pas pertinent pour la résolution implicite.
Le problème est qu'après l'effacement du type, les deux méthodes sont strictement identiques. BTW, même sans effacement, le site d'appel peut être ambigu et si un I
compare (1, "1")
Et il y a à la foisInt => String
&String => Int
. IMHO, trop de magie est problématique ici.