J'ai aimé la simplicité et l'efficacité de la première solution de Miles Sabin, mais j'ai été un peu insatisfait du fait que l'erreur que nous obtenons n'est pas très utile :
Par exemple avec la définition suivante :
def f[T]( implicit e: T =!= String ) {}
Essayer de faire f[String]
ne pourra pas être compilé avec :
<console>:10: error: ambiguous implicit values:
both method neqAmbig1 in object =!= of type [A]=> =!=[A,A]
and method neqAmbig2 in object =!= of type [A]=> =!=[A,A]
match expected type =!=[String,String]
f[String]
^
Je préférerais que le compilateur me dise quelque chose du genre "T n'est pas différent de String". Il s'avère qu'il est assez facile d'ajouter encore un autre niveau d'implicites de telle sorte que l'on transforme l'expression "T" en "String". ambiguïté erreur en une implicite non trouvé erreur. A partir de là, nous pouvons utiliser le implicitNotFound
pour émettre un message d'erreur personnalisé :
@annotation.implicitNotFound(msg = "Cannot prove that ${A} =!= ${B}.")
trait =!=[A,B]
object =!= {
class Impl[A, B]
object Impl {
implicit def neq[A, B] : A Impl B = null
implicit def neqAmbig1[A] : A Impl A = null
implicit def neqAmbig2[A] : A Impl A = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A =!= B = null
}
Essayons maintenant d'appeler f[String]
:
scala> f[String]
<console>:10: error: Cannot prove that String =!= String.
f[String]
^
C'est mieux. Merci au compilateur.
Comme dernière astuce pour ceux qui aiment le sucre syntaxique lié au contexte, on peut définir cet alias (basé sur les lambdas de type) :
type IsNot[A] = { type λ[B] = A =!= B }
Nous pouvons alors définir f
comme ça :
def f[T:IsNot[String]#λ] {}
La question de savoir s'il est plus facile à lire est hautement subjective. Dans tous les cas, c'est certainement plus court que d'écrire la liste complète des paramètres implicites.
UPDATE : Pour être complet, voici le code équivalent pour exprimer que A
n'est pas un sous-type de B
:
@annotation.implicitNotFound(msg = "Cannot prove that ${A} <:!< ${B}.")
trait <:!<[A,B]
object <:!< {
class Impl[A, B]
object Impl {
implicit def nsub[A, B] : A Impl B = null
implicit def nsubAmbig1[A, B>:A] : A Impl B = null
implicit def nsubAmbig2[A, B>:A] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <:!< B = null
}
type IsNotSub[B] = { type λ[A] = A <:!< B }
Et pour avoir exprimé que A
n'est pas convertible en B
:
@annotation.implicitNotFound(msg = "Cannot prove that ${A} <%!< ${B}.")
trait <%!<[A,B]
object <%!< {
class Impl[A, B]
object Impl {
implicit def nconv[A, B] : A Impl B = null
implicit def nconvAmbig1[A<%B, B] : A Impl B = null
implicit def nconvAmbig2[A<%B, B] : A Impl B = null
}
implicit def foo[A,B]( implicit e: A Impl B ): A <%!< B = null
}
type IsNotView[B] = { type λ[A] = A <%!< B }