Qu’est-ce que le contexte Scala et les limites de vue?

De manière simple, que sont les limites de contexte et de 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 oui, la question n’est pas apparente dans la barre “connexe”. Donc, la voici:

Qu’est-ce qu’un View Bound?

Une vue liée était un mécanisme introduit dans Scala pour permettre l’utilisation de certains types A comme s’il s’agissait de 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 B méthodes B sur un object de type A L'usage le plus courant des limites d'affichage 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 Ordered[A] , et que Ordered[A] définit la méthode <(other: A): Boolean , je peux utiliser l'expression a < b .

Sachez que les limites de 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 les fonctionnalités fournies par les classes de type Haskell, mais de manière plus détaillée.

Bien qu'une vue liée puisse être utilisée avec des types simples (par exemple, A <% String ), une liaison de contexte nécessite un type paramétré , tel que Ordered[A] ci-dessus, mais différent de Ssortingng .

Un lien de contexte 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] 

Cela est plus déroutant que la vue liée car il n'est pas clair comment l'utiliser. L'exemple commun 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é d'un ClassManifest , pour des raisons ClassManifest 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 on utilise la valeur implicite que nous voulons, celle de type Ordering[A] , quelle classe définit la méthode compare(a: A, b: A): Int .

Nous verrons une autre façon de le faire ci-dessous.

Comment les limites de vue et les limites de contexte sont-elles mises en œuvre?

Il ne devrait pas être surprenant que les limites de vue et les limites de contexte soient implémentées avec des parameters implicites, compte tenu de leur définition. En fait, la syntaxe que j'ai montrée est celle des sucres syntaxiques pour ce qui se passe réellement. Voyez ci-dessous comment ils dé-sucre:

 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) 

À quoi servent les limites de vue?

Les limites de vue sont principalement utilisées pour tirer parti du modèle PIMP de ma bibliothèque , par lequel on "ajoute" des méthodes à une classe existante, dans des situations où vous souhaitez renvoyer le type original d'une manière ou d'une autre. Si vous n'avez pas besoin de retourner ce type, vous n'avez pas besoin d'une vue liée.

L'exemple classique de l'utilisation liée à la vue est la gestion des 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 elle 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 les 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 f n'a pas besoin de le savoir.

Outre Ordered , l'utilisation la plus courante de la bibliothèque est la gestion de Ssortingng 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 le faire sans afficher les limites, le type de retour d'une Ssortingng serait une WrappedSsortingng (Scala 2.8), et de la même manière pour une Array .

La même chose se produit même si le type est uniquement utilisé comme paramètre de type du type de retour:

 def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted 

A quoi servent les Context Bounds?

Les limites de contexte sont principalement utilisées dans ce qui est connu sous le nom de modèle de typeclass , en référence aux classes de type de Haskell. Fondamentalement, ce modèle implémente une alternative à l'inheritance en rendant les fonctionnalités disponibles via une sorte de modèle d'adaptateur implicite.

L'exemple classique est la Ordering Scala 2.8, qui a remplacé Ordered dans la bibliothèque de Scala. L'utilisation est la suivante:

 def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b 

Bien que vous verrez habituellement 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 permettent 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 CanBuildFrom , mais il y a déjà une très longue réponse à ce sujet, alors je l'éviterai ici. Et, comme mentionné précédemment, il y a l'utilisation de ClassManifest , qui est nécessaire pour initialiser de nouveaux tableaux sans types concrets.

Le contexte lié à la typeclass est beaucoup plus susceptible d'être utilisé par vos propres classes, car elles permettent de séparer les préoccupations, alors que les limites de vue peuvent être évitées dans votre propre code par une bonne conception (il est surtout utilisé pour contourner la conception de quelqu'un ).

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 est la bibliothèque Scalaz, qui apporte beaucoup de puissance à Haskell pour Scala. Je recommande de lire sur les schémas de typage pour en apprendre davantage sur la manière dont il peut être utilisé.

MODIFIER

Questions d'intérêt connexes:

  • Une discussion sur les types, l'origine et la préséance des implicits
  • Chaînage des implicits