0
votes

Comment surcharger une méthode générique avec des preuves différentes sans ambiguïté?

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é?


1 commentaires

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 fois Int => String & String => Int . IMHO, trop de magie est problématique ici.


3 Réponses :


1
votes

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:

https://stackoverflow.com/a/16865745/2508492


2 commentaires

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.



2
votes

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")))


0 commentaires

1
votes

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>


2 commentaires

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.