4 votes

Scala : le passage d'un type contravariant comme paramètre implicite ne choisit pas le supertype le plus proche ?

Pourquoi le code suivant ne reprend pas la val implicite avec le supertype le plus proche ?

class A
class B extends A

trait TC[-T] { def show(t: T): String }

implicit val showA = new TC[A] { def show(a: A): String = "it's A" }
implicit val showB = new TC[B] { def show(b: B): String = "it's B" }

def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))

doit(new A) // "it's A" as expected
doit(new B) // "it's A" ... why does this not give "it's B" ???

Si vous faites TC invariant (c'est-à-dire trait TC[T] (...) ), alors cela fonctionne bien et doit(new B) retourne "c'est B" comme prévu.

En ajoutant un autre implicite pour le type Any ce problème est encore plus extrême :

class A
class B extends A

trait TC[-T] { def show(t: T): String }

implicit val showA = new TC[A] { def show(a: A): String = "it's A" }
implicit val showB = new TC[B] { def show(b: B): String = "it's B" }
implicit val showAny = new TC[Any] { def show(x: Any): String = "it's Any" }

def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))

doit(new A) // "it's Any" ... why does this not give "it's A" ???
doit(new B) // "it's Any" ... why does this not give "it's B" ???

Et encore une fois, ça marche bien si TC est invariant.

Qu'est-ce qui se passe ici, et comment le résoudre ? Mon objectif est d'avoir un système contravariant TC qui choisit implicitement le supertype approprié le plus proche.

6voto

Andrey Tyukin Points 29032

Desde TC[-T] est contravariant dans son argument de type, TC[A] est un sous-type de TC[B] et est donc considéré comme plus "spécifique". Il s'agit d'une décision de conception bien connue (et quelque peu controversée), qui signifie essentiellement que la résolution implicite avec contravariance se comporte parfois de manière assez inattendue.


Palliatif 1 : hiérarchisation des implicites à l'aide de l'héritage

Voici comment vous pourriez utiliser l'héritage et le modèle "LowPriority-*-Implicits" :

class A
class B extends A
class C extends B
class D extends C

trait TC[-T] { def show(t: T): String }

trait LowPriorityFallbackImplicits {
  implicit def showA[X <: A]: TC[X] = 
    new TC[A] { def show(a: A): String = "it's A" }
}
object TcImplicits extends LowPriorityFallbackImplicits {
  implicit def showC[X <: C]: TC[X] = 
    new TC[C] { def show(c: C): String = "it's C" }
}

def doit[X](x: X)(implicit tc: TC[X]): Unit = println(tc.show(x))

import TcImplicits._

doit(new A)
doit(new B)
doit(new C)
doit(new D)

Maintenant, il choisit le plus spécifique dans tous les cas :

it's A
it's A
it's C
it's C

Palliatif 2 : trait d'aide invariant

Voici comment vous pouvez échanger de force les implicites dans votre exemple particulier en introduisant un trait auxiliaire qui est invariant dans l'argument du type :

class A
class B extends A

trait TC[-T] { def show(t: T): String }

val showA = new TC[A] { def show(a: A): String = "it's A" }
val showB = new TC[B] { def show(b: B): String = "it's B" }

trait TcImplicit[X] { def get: TC[X] }
implicit val showAImplicit = new TcImplicit[A] { def get = showA }
implicit val showBImplicit = new TcImplicit[B] { def get = showB }

def doit[X](x: X)(implicit tc: TcImplicit[X]): Unit = println(tc.get.show(x))

doit(new A)
doit(new B)

imprime

it's A
it's B

3voto

S'il y a plusieurs arguments éligibles qui correspondent au type du paramètre implicite, une balise le plus spécifique une sera choisie en utilisant les règles de résolution de surcharge statique. Si le paramètre a un argument par défaut et qu'aucun argument implicite ne peut être trouvé, l'argument par défaut est utilisé.

- Spécification du langage Scala - version 2.12

Fondamentalement, parce que vous rendez votre TypeClass contravariante, cela signifie que TC[Any] <:< TC[A] <:< TC[B] (où <:< signifie sous-type) . Et pour cela TC[Any] est considéré comme le plus spécifique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X