Je peux voir dans la documentation de l'API pour Predef qu'il s'agit de sous-classes d'un type de fonction générique (From) => To, mais c'est tout ce qu'il dit. Euh, quoi? Peut-être qu'il y a de la documentation quelque part, mais les moteurs de recherche ne gèrent pas très bien les "noms" comme "<: <", donc je n'ai pas pu le trouver.
Question de suivi: quand dois-je utiliser ces symboles / classes géniaux, et pourquoi?
typeclass
es de Haskell font-ils le travail de ces opérateurs? Exemplecompare :: Ord a => a -> a -> Ordering
:? J'essaie de comprendre ce concept Scala par rapport à son homologue Haskell.Réponses:
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:
L'argument implicite
evidence
est fourni par le compilateur, iffA
isString
. Vous pouvez penser comme une preuve queA
estString
--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 deA
àString
, ce qui vous permet d'appelera.length
et de ne pas crier sur le compilateur]Maintenant, je peux l'utiliser comme ça:
Mais si j'ai essayé de l'utiliser avec un
Foo
contenant autre chose qu'unString
: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 deA
ce qui estFoo
généralement requis; à savoir, vous ne pouvez invoquer quegetStringLength
sur unFoo[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 BA <:< 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 ancienList
, juste unList[Int]
. Cependant, leList
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é sursumInts
, vous pouvez vous assurer que cette méthode a une contrainte supplémentaire qu'elle ne peut être utilisée que sur unList[Int]
. Essentiellement, vous écrivez du code spécial pour certains types de listes.la source
Manifest
que vous n'avez pas mentionnées.Manifest
sont<:<
et>:>
seulement ... puisque OP a mentionné exactement les 3 variétés de contraintes de type généralisées, je suppose que c'est ce qui l'intéressait.class =:=[From, To] extends From => To
, ce qui signifie qu'une valeur implicite de typeFrom =:= To
est en fait une conversion implicite deFrom
àTo
. Donc, en acceptant un paramètre implicite de type,A =:= String
vous dites qu'ilA
peut être implicitement converti enString
. Si vous modifiez l'ordre et que l'argument implicite est de typeString =:= A
, cela ne fonctionnera pas, car ce serait une conversion implicite deString
àA
.From =:= To
dans la portée implique que vous avez une conversion impliciteFrom => To
, mais l'implication ne s'exécute pas à l'envers; avoir une conversion impliciteA => B
n'implique pas que vous ayez une instance deA =:= B
.=:=
est une classe abstraite scellée définie dansscala.Predef
, et n'a qu'une seule instance exposée publiquement, qui est implicite et est de typeA =:= A
. Vous êtes donc assuré qu'une valeur implicite de typeA =:= B
témoigne du fait queA
etB
sont égaux.Pas une réponse complète (d'autres ont déjà répondu à cela), je voulais juste noter ce qui suit, ce qui peut peut-être aider à mieux comprendre la syntaxe: La façon dont vous utilisez normalement ces "opérateurs", comme par exemple dans l'exemple de pelotom:
utilise la syntaxe alternative de Scala pour les opérateurs de type .
Donc,
A =:= String
c'est la même chose que=:=[A, String]
(et=:=
c'est juste une classe ou un trait avec un nom fantaisiste). Notez que cette syntaxe fonctionne également avec les classes "normales", par exemple vous pouvez écrire:comme ça:
Elle est similaire aux deux syntaxes pour les appels de méthode, la "normale" avec
.
et()
et la syntaxe d'opérateur.la source
makes use of Scala's alternative infix syntax for type operators.
manque totalement cette explication sans laquelle le tout n'a pas de sensLisez les autres réponses pour comprendre quelles sont ces constructions. Voici quand vous devez les utiliser. Vous les utilisez lorsque vous devez contraindre une méthode pour des types spécifiques uniquement.
Voici un exemple. Supposons que vous souhaitiez définir une paire homogène, comme ceci:
Maintenant, vous voulez ajouter une méthode
smaller
, comme ceci:Cela ne fonctionne que si
T
est commandé. Vous pouvez restreindre la classe entière:Mais cela semble dommage - il pourrait y avoir des utilisations pour la classe quand elle
T
n'est pas commandée. Avec une contrainte de type, vous pouvez toujours définir lasmaller
méthode:C'est correct d'instancier, disons, a
Pair[File]
, tant que vous ne faites pas appelsmaller
à cela.Dans le cas de
Option
, les implémenteurs voulaient uneorNull
méthode, même si cela n'a pas de sensOption[Int]
. En utilisant une contrainte de type, tout va bien. Vous pouvez utiliserorNull
sur unOption[String]
, et vous pouvez former unOption[Int]
et l'utiliser, tant que vous ne faites pas appelorNull
à lui. Si vous essayezSome(42).orNull
, vous obtenez le charmant messagela source
<:<
, et je pense que l'Ordered
exemple est pas plus convaincant puisque vous maintenant plutôt utiliser laOrdering
classe de types plutôt que leOrdered
trait. Quelque chose comme:def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second
.Cela dépend de l'endroit où ils sont utilisés. Le plus souvent, lorsqu'ils sont utilisés lors de la déclaration de types de paramètres implicites, ce sont des classes. Ils peuvent aussi être des objets dans de rares cas. Enfin, ils peuvent être des opérateurs sur des
Manifest
objets. Ils sont définis à l'intérieurscala.Predef
dans les deux premiers cas, mais pas particulièrement bien documentés.Ils sont destinés à fournir un moyen de tester la relation entre les classes, tout comme
<:
et le<%
font, dans des situations où ces dernières ne peuvent pas être utilisées.Quant à la question "quand dois-je les utiliser?", La réponse est que vous ne devriez pas, à moins que vous sachiez que vous devriez. :-) EDIT : Ok, ok, voici quelques exemples de la bibliothèque. Sur
Either
, vous avez:Sur
Option
, vous avez:Vous trouverez d'autres exemples sur les collections.
la source
:-)
un autre? Et je conviens que votre réponse à "Quand dois-je les utiliser?" s'applique à beaucoup de choses.