De manière simple, quelles sont les limites du contexte et de la vue et quelle est la différence entre elles?
Quelques exemples faciles à suivre seraient également intéressants!
Je pensais que cela avait déjà été demandé, mais si c'est le cas, la question n'apparaît pas dans la barre "connexe". Alors, la voici:
Un view lié était un mécanisme introduit dans Scala pour permettre l'utilisation d'un type A
comme si c'était 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, de sorte que l'on puisse appeler des méthodes B
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, en tout cas) 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 Ordered[A]
, Et parce que Ordered[A]
Définit la méthode <(other: A): Boolean
, je peux utiliser l'expression a < b
.
S'il vous plaît être conscient que les limites de vue sont obsolètes , vous devriez 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 type class pattern, un modèle de code qui émule la fonctionnalité fournie par les classes de type Haskell. mais d'une manière plus verbeuse.
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é, tel que Ordered[A]
Ci-dessus, mais contrairement à String
.
Un contexte lié décrit un implicite valeur, au lieu de l'implicite lié à la vue conversion. Il est utilisé pour déclarer que pour certains types A
, une valeur implicite de type B[A]
Est disponible. La syntaxe est la suivante:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
Ceci est plus déroutant que la vue liée car il n’est pas 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 initialisation Array
sur un type paramétré nécessite la disponibilité de ClassManifest
, pour des raisons obscures liées à l'effacement de type et à la nature non effaçante 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 souhaitée, celle du type Ordering[A]
, Classe qui définit la méthode compare(a: A, b: A): Int
.
Nous verrons une autre façon de faire cela ci-dessous.
Il ne devrait pas être surprenant que les limites de vue et de contexte soient implémentées avec des paramètres implicites, en fonction de leur définition. En fait, la syntaxe que j'ai montrée sont des sucres syntaxiques pour ce qui se passe réellement. 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 de 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, grâce auquel on "ajoute" des méthodes à une classe existante, dans les cas où vous souhaitez renvoyer le type d'origine. Si vous n'avez pas besoin de renvoyer ce type, vous n'avez pas besoin d'une vue limitée.
L'exemple classique d'utilisation liée à la vue est la gestion de Ordered
. Notez que Int
n'est pas Ordered
, par exemple, bien qu'il y ait une conversion implicite. L'exemple donné précédemment nécessite 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 de vue. Cependant, si je devais retourner un autre type, je n'aurais 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
, de sorte que f
n'a pas besoin de le savoir.
Outre Ordered
, l'utilisation la plus courante de la bibliothèque est de manipuler String
et Array
, qui sont des classes Java, comme elles étaient Scala collections. Par exemple:
def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b
Si vous tentez de le faire sans limite de vue, le type de retour d'un String
serait un WrappedString
(Scala 2.8) et de la même manière 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 typeclass pattern, en tant que 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é Ordered
dans 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 voyiez 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 de Ordering
qui activent le style d'opérateur traditionnel. Un autre exemple dans Scala 2.8 est le Numeric
:
def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)
Un exemple plus complexe est la nouvelle utilisation de la collection de CanBuildFrom
, mais la réponse à cette question est déjà très longue, je vais donc l'éviter ici. Et, comme mentionné précédemment, il y a l'utilisation de ClassManifest
, nécessaire pour initialiser de nouveaux tableaux sans types concrets.
Le contexte lié au modèle de classe de types est beaucoup plus susceptible d'être utilisé par vos propres classes, car elles permettent de séparer les problèmes, alors 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 frameworks les plus importants de Scala. L’exemple le plus extrême de son utilisation, cependant, est la bibliothèque Scalaz, qui apporte beaucoup de puissance de Haskell à Scala. Je vous recommande de lire sur les modèles de classes de types pour mieux vous familiariser avec toutes les utilisations possibles.
[~ # ~] éditer [~ # ~]
Questions connexes d'intérêt: