Que signifient <: <, <% <et =: = dans Scala 2.8, et où sont-ils documentés?

201

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?

Jeff
la source
6
Voici une question connexe qui peut répondre au moins partiellement à votre question: stackoverflow.com/questions/2603003/operator-in-scala
Yardena
13
symbolhound.com est votre ami de recherche de code :)
ron
Les typeclasses de Haskell font-ils le travail de ces opérateurs? Exemple compare :: Ord a => a -> a -> Ordering:? J'essaie de comprendre ce concept Scala par rapport à son homologue Haskell.
Kevin Meredith

Réponses:

217

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 evidenceest fourni par le compilateur, iff Ais String. Vous pouvez penser comme une preuve que Aest 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.lengthet 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 Foocontenant 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! getStringLengthimpose des restrictions supplémentaires sur le type de Ace qui est Foogénéralement requis; à savoir, vous ne pouvez invoquer que getStringLengthsur 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 <:< Bsignifie que A doit être un sous-type de B (analogue à la contrainte de type simple<: )
  • A <%< Bsignifie 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.sumIntsmé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 Listconstructeur 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.

Tom Crockett
la source
3
Bon, ok, mais il y a aussi des méthodes du même nom Manifestque vous n'avez pas mentionnées.
Daniel C.Sobral
3
Les méthodes Manifestsont <:<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.
Tom Crockett
12
@IttayD: c'est assez intelligent ... class =:=[From, To] extends From => To, ce qui signifie qu'une valeur implicite de type From =:= Toest en fait une conversion implicite de Fromà To. Donc, en acceptant un paramètre implicite de type, A =:= Stringvous dites qu'il Apeut être implicitement converti en String. Si vous modifiez l'ordre et que l'argument implicite est de type String =:= A, cela ne fonctionnera pas, car ce serait une conversion implicite de Stringà A.
Tom Crockett
25
Ces symboles à trois caractères ont-ils des noms? Mon problème avec la soupe aux symboles de Scala est qu'il est difficile d'en parler verbalement, et il est pratiquement impossible d'utiliser Google ou tout autre moteur de recherche pour trouver des discussions et des exemples de leur utilisation.
Gigatron
4
@Andrea Nope, cela ne fonctionnera que si les types sont exactement égaux. Notez que j'ai dit qu'une valeur implicite de type From =:= Todans la portée implique que vous avez une conversion implicite From => To, mais l'implication ne s'exécute pas à l'envers; avoir une conversion implicite A => Bn'implique pas que vous ayez une instance de A =:= B. =:=est une classe abstraite scellée définie dans scala.Predef, et n'a qu'une seule instance exposée publiquement, qui est implicite et est de type A =:= A. Vous êtes donc assuré qu'une valeur implicite de type A =:= Btémoigne du fait que Aet Bsont égaux.
Tom Crockett
55

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:

def getStringLength(implicit evidence: A =:= String)

utilise la syntaxe alternative de Scala pour les opérateurs de type .

Donc, A =:= Stringc'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:

val a: Tuple2[Int, String] = (1, "one")

comme ça:

val a: Int Tuple2 String = (1, "one")

Elle est similaire aux deux syntaxes pour les appels de méthode, la "normale" avec .et ()et la syntaxe d'opérateur.

Jesper
la source
2
a besoin d'un vote positif car il makes use of Scala's alternative infix syntax for type operators.manque totalement cette explication sans laquelle le tout n'a pas de sens
Ovidiu Dolha
39

Lisez 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:

class Pair[T](val first: T, val second: T)

Maintenant, vous voulez ajouter une méthode smaller, comme ceci:

def smaller = if (first < second) first else second

Cela ne fonctionne que si Test commandé. Vous pouvez restreindre la classe entière:

class Pair[T <: Ordered[T]](val first: T, val second: T)

Mais cela semble dommage - il pourrait y avoir des utilisations pour la classe quand elle Tn'est pas commandée. Avec une contrainte de type, vous pouvez toujours définir la smallerméthode:

def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second

C'est correct d'instancier, disons, a Pair[File], tant que vous ne faites pas appel smaller à cela.

Dans le cas de Option, les implémenteurs voulaient une orNullméthode, même si cela n'a pas de sens Option[Int]. En utilisant une contrainte de type, tout va bien. Vous pouvez utiliser orNullsur un Option[String], et vous pouvez former un Option[Int]et l'utiliser, tant que vous ne faites pas appel orNullà lui. Si vous essayez Some(42).orNull, vous obtenez le charmant message

 error: Cannot prove that Null <:< Int
Cayhorstmann
la source
2
Je me rends compte que ce ans après cette réponse, mais je suis à la recherche pour les cas d'utilisation pour <:<, et je pense que l' Orderedexemple est pas plus convaincant puisque vous maintenant plutôt utiliser la Orderingclasse de types plutôt que le Orderedtrait. Quelque chose comme: def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
ebruchez
1
@ebruchez: un cas d'utilisation est pour le codage des types d'union dans une scala non modifiée, voir milessabin.com/blog/2011/06/09/scala-union-types-curry-howard
17

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 Manifestobjets. Ils sont définis à l'intérieur scala.Predefdans 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:

/**
  * Joins an <code>Either</code> through <code>Right</code>.
  */
 def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
   case Left(a)  => Left(a)
   case Right(b) => b
 }

 /**
  * Joins an <code>Either</code> through <code>Left</code>.
  */
 def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
   case Left(a)  => a
   case Right(b) => Right(b)
 }

Sur Option, vous avez:

def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null

Vous trouverez d'autres exemples sur les collections.

Daniel C. Sobral
la source
En est-il :-)un autre? Et je conviens que votre réponse à "Quand dois-je les utiliser?" s'applique à beaucoup de choses.
Mike Miller
"Ils sont destinés à fournir un moyen de tester la relation entre les classes" <- trop général pour être utile
Jeff
3
"Quant à la question" quand dois-je les utiliser? ", La réponse est que vous ne devriez pas, sauf si vous savez que vous devriez." <- C'est pourquoi je demande. J'aimerais pouvoir prendre cette décision par moi-même.
Jeff