Celles-ci sont appelées contraintes de type généralisées . Ils vous permettent, à partir d'une classe ou d'un trait paramétré par type, de contraindre davantage l' un de ses paramètres de type. Voici un exemple:
case class Foo[A](a:A) { // 'A' can be substituted with any type
// getStringLength can only be used if this is a Foo[String]
def getStringLength(implicit evidence: A =:= String) = a.length
}
L'argument implicite evidence
est fourni par le compilateur, iff A
is String
. Vous pouvez penser comme une preuve que A
est String
--Le argument lui - même n'a pas d' importance, ne sachant qu'il existe. [edit: eh bien, techniquement, c'est réellement important parce qu'il représente une conversion implicite de A
à String
, ce qui vous permet d'appeler a.length
et de ne pas crier sur le compilateur]
Maintenant, je peux l'utiliser comme ça:
scala> Foo("blah").getStringLength
res6: Int = 4
Mais si j'ai essayé de l'utiliser avec un Foo
contenant autre chose qu'un String
:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
Vous pouvez lire cette erreur comme "n'a pas pu trouver la preuve que Int == String" ... c'est comme ça! getStringLength
impose des restrictions supplémentaires sur le type de A
ce qui est Foo
généralement requis; à savoir, vous ne pouvez invoquer que getStringLength
sur un Foo[String]
. Cette contrainte est appliquée au moment de la compilation, ce qui est cool!
<:<
et <%<
fonctionnent de la même manière, mais avec de légères variations:
A =:= B
signifie que A doit être exactement B
A <:< B
signifie que A doit être un sous-type de B (analogue à la contrainte de type simple<:
)
A <%< B
signifie que A doit être visible comme B, éventuellement via une conversion implicite (analogue à la contrainte de type simple <%
)
Cet extrait de @retronym est une bonne explication de la façon dont ce genre de choses était accompli et comment les contraintes de type généralisées facilitent maintenant.
ADDENDA
Pour répondre à votre question de suivi, il est vrai que l'exemple que j'ai donné est assez artificiel et n'est évidemment pas utile. Mais imaginez l'utiliser pour définir quelque chose comme une List.sumInts
méthode, qui additionne une liste d'entiers. Vous ne voulez pas autoriser cette méthode à être invoquée sur n'importe quel ancien List
, juste un List[Int]
. Cependant, le List
constructeur de type ne peut pas être aussi contraint; vous voulez toujours pouvoir avoir des listes de chaînes, foos, barres et autres joyeusetés. Ainsi, en plaçant une contrainte de type généralisé sur sumInts
, vous pouvez vous assurer que cette méthode a une contrainte supplémentaire qu'elle ne peut être utilisée que sur un List[Int]
. Essentiellement, vous écrivez du code spécial pour certains types de listes.