211 votes

Que faire < :<, < % < et = : = moyenne au Scala 2.8, et où ils sont documentés ?

Je vois dans la documentation de l’API pour Predef qu’ils sont des sous-classes d’un type de fonction générique (de) => à, mais c’est tout qu'il dit. Euh, quoi ? Peut-être il y a une documentation quelque part, mais les moteurs de recherche ne pas manipuler des « noms » comme « 

Question de suivi : Quand dois-je utiliser ces symboles funky/classes et pourquoi ?

233voto

pelotom Points 14817

Ceux-ci sont appelés généralisée type de contraintes. Ils vous permettent, à partir de l'intérieur d'un type paramétré de classe ou de trait, à restreignent encore 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 est String. Vous pouvez la considérer comme une preuve qu' A est String--l'argument lui-même n'est pas important, seule en sachant qu'il en existe. [edit: eh bien, techniquement, il est réellement important, car il représente une conversion implicite de A de String, ce qui est ce que vous permet de téléphoner a.length et ne pas avoir le compilateur hurler à vous]

Maintenant je peux l'utiliser comme ceci:

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 y lire que le message d'erreur "ne pouvait pas trouver des preuves d'Int == Chaîne de caractères"... c'est comme il se doit! getStringLength est d'imposer de nouvelles restrictions sur le type d' A de ce qu' Foo en général l'exige; à savoir, vous ne pouvez invoquer getStringLength sur Foo[String]. Cette contrainte est appliquée au moment de la compilation, ce qui est cool!

<:< et <%< fonctionnent de la même façon, mais avec de légères variations:

  • A =:= B signifie Un doit être exactement de B
  • A <:< B signifie Un doit être un sous-type de B (analogue à la simple contrainte de type <:)
  • A <%< B signifie Un doit être visible en tant que B, éventuellement par l'intermédiaire de la conversion implicite (analogue à la simple contrainte de type <%)

Cet extrait par @retronym est une bonne explication de la façon dont ce genre de chose qui sert à faire et comment généralisée des contraintes de type de le rendre plus facile maintenant.

ADDENDUM

Pour répondre à votre question, il est vrai que l'exemple que j'ai donné est assez artificiel et pas forcément utile. Mais imaginez l'utiliser pour définir quelque chose comme un List.sumInts méthode, qui s'ajoute à une liste d'entiers. Vous ne souhaitez pas autoriser cette méthode pour être appelé sur un vieux - List, juste un List[Int]. Cependant, l' List type constructeur ne peut pas être contraintes; vous voulez toujours être en mesure d'avoir des listes de chaînes, foos, des bars, et dressoirs. Ainsi en plaçant une généralisé type de contrainte sur sumInts, vous pouvez vous assurer que juste que la méthode a une contrainte supplémentaire qu'il peut seulement être utilisé sur un List[Int]. Essentiellement, vous êtes en train de rédiger spéciaux-code cas pour certains types de listes.

58voto

Jesper Points 65733

Pas une réponse complète (d'autres ont déjà répondu à cette question), je voulais juste noter les points suivants, qui peut-être utile de comprendre la syntaxe de mieux: de La façon dont vous utilisez habituellement ces "opérateurs", comme par exemple dans pelotom de l'exemple:

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

rend l'utilisation de Scala alternative infix syntaxe de type opérateurs.

Donc, A =:= String est le même que =:=[A, String] (et =:= est juste une classe ou d'un trait avec une fantaisie à la recherche du nom). Notez que cette syntaxe fonctionne aussi avec "régulière" des classes, par exemple, vous pouvez écrire:

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

comme ceci:

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

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

42voto

cayhorstmann Points 1274

Lire les autres réponses pour comprendre ce que ces constructions sont. Ici est lorsque vous devez les utiliser. Vous les utilisez quand vous avez besoin de contraindre une méthode pour des types spécifiques seulement.

Ici est un exemple. Supposons que vous souhaitez définir un binôme 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

Qui ne fonctionne que si T est commandé. Vous pouvez restreindre l'ensemble de la classe:

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

Mais cela semble une honte--il peut être utilise pour la classe lors de la T n'est pas commandé. Avec une contrainte de type, vous pouvez toujours définir l' smaller méthode:

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

C'est ok pour instancier, disons, un Pair[File], tant que vous ne l'appelez pas smaller .

Dans le cas d' Option, les réalisateurs voulaient un orNull méthode, même si elle ne fait pas de sens pour Option[Int]. En utilisant un type de contrainte, tout est bien. Vous pouvez utiliser orNull sur Option[String], et vous pouvez former un Option[Int] et de l'utiliser, aussi longtemps que vous ne l'appelez pas orNull . Si vous essayez Some(42).orNull, vous obtenez le charmant message

 error: Cannot prove that Null <:< Int

17voto

Daniel C. Sobral Points 159554

Il dépend de l'endroit où ils sont utilisés. Le plus souvent, lorsqu'il est utilisé, tout en déclarant types de paramètres implicites, ils sont classes. Ils peuvent être aussi des objets dans de rares cas. Enfin, ils peuvent être des opérateurs sur Manifest objets. Elles sont définies à l'intérieur d' scala.Predef dans les deux premiers cas, bien que pas particulièrement bien documenté.

Ils sont destinés à fournir un moyen de tester la relation entre les classes, tout comme <: et <% le faire, dans les situations où ce dernier ne peut pas être utilisé.

Comme pour la question "quand dois-je utiliser?", la réponse est que vous ne devriez pas, sauf si vous savez que vous devriez. :-) EDIT: Ok, ok, voici quelques exemples à partir de la bibliothèque. Sur Either, vous devez:

/**
  * 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 devez:

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

Vous trouverez d'autres exemples sur les collections.

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