De manière simple, quelles sont les limites de contexte et de vue et quelle est la différence entre elles?
Quelques exemples faciles à suivre seraient également formidables!
Je pensais que cela avait déjà été posé, mais, dans l'affirmative, la question n'apparaît pas dans la barre "connexes". Voici donc:
Une vue liée était un mécanisme introduit dans Scala pour permettre l'utilisation d'un certain type A
comme s'il s'agissait d'un type B
. La syntaxe typique est la suivante:
def f[A <% B](a: A) = a.bMethod
En d'autres termes, A
devrait avoir une conversion implicite en B
disponible, afin que l'on puisse appeler des B
méthodes sur un objet de type A
. L'utilisation la plus courante des limites de vue dans la bibliothèque standard (avant Scala 2.8.0, de toute façon), est avec Ordered
, comme ceci:
def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b
Parce que l'on peut convertir A
en un Ordered[A]
, et parce que Ordered[A]
définit la méthode <(other: A): Boolean
, je peux utiliser l'expression a < b
.
Veuillez noter que les limites de la vue sont obsolètes , vous devez les éviter.
Les limites de contexte ont été introduites dans Scala 2.8.0 et sont généralement utilisées avec le modèle de classe de type , un modèle de code qui émule la fonctionnalité fournie par les classes de type Haskell, mais de manière plus détaillée.
Bien qu'une limite de vue puisse être utilisée avec des types simples (par exemple, A <% String
), une limite de contexte nécessite un type paramétré , comme Ordered[A]
ci-dessus, mais contrairement à String
.
Un contexte lié décrit une valeur implicite , au lieu de la conversion implicite de la vue liée . Il est utilisé pour déclarer que pour certains types A
, il existe une valeur implicite de type B[A]
disponible. La syntaxe va comme ceci:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
C'est plus déroutant que la vue liée car il n'est pas immédiatement clair comment l'utiliser. L'exemple courant d'utilisation dans Scala est le suivant:
def f[A : ClassManifest](n: Int) = new Array[A](n)
Une Array
initialisation sur un type paramétré nécessite la ClassManifest
disponibilité de a, pour des raisons obscures liées à l'effacement de type et à la nature non effaçable des tableaux.
Un autre exemple très courant dans la bibliothèque est un peu plus complexe:
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
Ici, implicitly
est utilisé pour récupérer la valeur implicite que nous voulons, une de type Ordering[A]
, dont la classe définit la méthode compare(a: A, b: A): Int
.
Nous verrons une autre façon de procéder ci-dessous.
Il ne devrait pas être surprenant que les limites de vue et les limites de contexte soient implémentées avec des paramètres implicites, compte tenu de leur définition. En fait, la syntaxe que j'ai montrée est des sucres syntaxiques pour ce qui se passe vraiment. Voir ci-dessous comment ils dégraissent:
def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod
def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)
Donc, naturellement, on peut les écrire dans leur syntaxe complète, ce qui est particulièrement utile pour les limites du contexte:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
Les limites de vue sont principalement utilisées pour tirer parti du modèle pimp my library , à travers lequel on "ajoute" des méthodes à une classe existante, dans les situations où vous souhaitez renvoyer le type d'origine d'une manière ou d'une autre. Si vous n'avez pas besoin de renvoyer ce type de quelque manière que ce soit, vous n'avez pas besoin d'une vue liée.
L'exemple classique d'utilisation liée à la vue est la manipulation Ordered
. Notez que ce Int
n'est pas le cas Ordered
, par exemple, bien qu'il y ait une conversion implicite. L'exemple donné précédemment a besoin d'une vue liée car il renvoie le type non converti:
def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b
Cet exemple ne fonctionnera pas sans limites d'affichage. Cependant, si je devais retourner un autre type, alors je n'ai plus besoin d'une vue liée:
def f[A](a: Ordered[A], b: A): Boolean = a < b
La conversion ici (si nécessaire) se produit avant que je passe le paramètre à f
, donc je n'ai f
pas besoin de le savoir.
En outre Ordered
, l'utilisation la plus courante de la bibliothèque est la manipulation String
et Array
, qui sont des classes Java, comme s'il s'agissait de collections Scala. Par exemple:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Si l'on essayait de faire cela sans voir les limites, le type de retour d'un String
serait unWrappedString
(Scala 2.8), et de même pour Array
.
La même chose se produit même si le type n'est utilisé que comme paramètre de type du type de retour:
def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted
Les limites de contexte sont principalement utilisées dans ce qui est devenu connu sous le nom de modèle de classe , comme référence aux classes de types de Haskell. Fondamentalement, ce modèle implémente une alternative à l'héritage en rendant la fonctionnalité disponible via une sorte de modèle d'adaptateur implicite.
L'exemple classique est celui de Scala 2.8 Ordering
, qui a remplacé l' Ordered
ensemble de la bibliothèque de Scala. L'utilisation est:
def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b
Bien que vous verrez généralement cela écrit comme ceci:
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
import ord.mkOrderingOps
if (a < b) a else b
}
Qui tirent parti de certaines conversions implicites à l'intérieur Ordering
qui permettent le style d'opérateur traditionnel. Un autre exemple dans Scala 2.8 est Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Un exemple plus complexe est l'utilisation de la nouvelle collection de CanBuildFrom
, mais il y a déjà une très longue réponse à ce sujet, donc je l'éviterai ici. Et, comme mentionné précédemment, il y a l' ClassManifest
utilisation, qui est nécessaire pour initialiser de nouveaux tableaux sans types concrets.
Le contexte lié au modèle de classe est beaucoup plus susceptible d'être utilisé par vos propres classes, car elles permettent de séparer les préoccupations, tandis que les limites de vue peuvent être évitées dans votre propre code par une bonne conception (il est principalement utilisé pour contourner la conception de quelqu'un d'autre) ).
Bien que cela soit possible depuis longtemps, l'utilisation des limites de contexte a vraiment pris son envol en 2010 et se retrouve maintenant dans une certaine mesure dans la plupart des bibliothèques et des cadres les plus importants de Scala. L'exemple le plus extrême de son utilisation, cependant, est la bibliothèque Scalaz, qui apporte une grande partie de la puissance de Haskell à Scala. Je recommande de lire sur les modèles de classes pour se familiariser avec toutes les façons dont il peut être utilisé.
ÉDITER
Questions d'intérêt connexes: