Que sont le contexte Scala et les limites de vue?

267

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!

chrsan
la source

Réponses:

477

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:

Qu'est-ce qu'un View Bound?

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, Adevrait avoir une conversion implicite en Bdisponible, afin que l'on puisse appeler des Bmé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 Aen 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.

Qu'est-ce qu'un contexte lié?

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 Arrayinitialisation sur un type paramétré nécessite la ClassManifestdisponibilité 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, implicitlyest 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.

Comment les limites d'affichage et les limites de contexte sont-elles implémentées?

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)

À quoi servent les limites d'affichage?

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 Intn'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 fpas besoin de le savoir.

En outre Ordered, l'utilisation la plus courante de la bibliothèque est la manipulation Stringet 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 Stringserait 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

À quoi servent les limites de contexte?

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' Orderedensemble 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 Orderingqui 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' ClassManifestutilisation, 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:

Daniel C. Sobral
la source
9
Merci beaucoup. Je sais que cela a déjà été répondu, et je n'ai peut-être pas lu assez attentivement alors, mais votre explication ici est la plus claire que j'ai vue. Alors merci encore.
chrsan
3
@chrsan J'ai ajouté deux autres sections, expliquant plus en détail où utiliser chacune d'elles.
Daniel C.Sobral
2
Je pense que c'est une excellente explication. J'aimerais traduire ceci pour mon blog allemand (dgronau.wordpress.com) si cela vous convient.
Landei
3
C'est de loin l'explication la meilleure et la plus complète de ce sujet que j'ai trouvée jusqu'à présent. Merci beaucoup!
fotNelton
2
Sooo, quand sortira ton livre Scala, et où puis-je l'acheter :)
wfbarksdale