Dans un sens, oui, les implicites représentent l'état global. Cependant, ils ne sont pas mutables, ce qui est le vrai problème avec les variables globales -- vous ne voyez pas les gens se plaindre des constantes globales, n'est-ce pas ? En fait, les normes de codage imposent généralement de transformer toutes les constantes de votre code en constantes ou enums, qui sont généralement globales.
Notez également que les implicites sont no dans un espace de nom plat, ce qui est également un problème courant avec les globaux. Ils sont explicitement liés aux types et, par conséquent, à la hiérarchie des paquets de ces types.
Prenez donc vos globaux, rendez-les immuables et initialisés à l'endroit de la déclaration, et placez-les dans des espaces de noms. Est-ce qu'ils ressemblent toujours à des globaux ? Sont-ils toujours problématiques ?
Mais ne nous arrêtons pas là. Implicites sont liés aux types, et ils sont tout aussi "globaux" que les types. Le fait que les types soient globaux vous dérange-t-il ?
Quant aux cas d'utilisation, ils sont nombreux, mais nous pouvons faire un bref rappel basé sur leur historique. À l'origine, afaik, Scala n'avait pas d'implicites. Ce que Scala avait, c'était des types de vue, une fonctionnalité que beaucoup d'autres langages avaient. Nous pouvons encore le constater aujourd'hui lorsque vous écrivez quelque chose comme T <% Ordered[T]
ce qui signifie que le type T
peut être considéré comme un type Ordered[T]
. Les types de vue sont un moyen de rendre les casts automatiques disponibles sur les paramètres de type (génériques).
Scala alors généralisé cette caractéristique avec les implicites. Les casts automatiques n'existent plus et, à la place, vous avez conversions implicites -- qui sont juste Function1
et, par conséquent, peuvent être transmises comme paramètres. Dès lors, T <% Ordered[T]
signifiait qu'une valeur pour une conversion implicite serait passée comme paramètre. Puisque la conversion est automatique, l'appelant de la fonction n'est pas obligé de passer explicitement le paramètre -- ainsi ces paramètres sont devenus paramètres implicites .
Notez qu'il y a deux concepts -- conversions implicites et paramètres implicites -- qui sont très proches, mais qui ne se recouvrent pas complètement.
Quoi qu'il en soit, les types de vues sont devenus un sucre syntaxique pour les conversions implicites passées implicitement. Ils seraient réécrits comme ceci :
def max[T <% Ordered[T]](a: T, b: T): T = if (a < b) b else a
def max[T](a: T, b: T)(implicit $ev1: Function1[T, Ordered[T]]): T = if ($ev1(a) < b) b else a
Les paramètres implicites sont simplement une généralisation de ce modèle, ce qui rend possible le passage de tout des paramètres implicites, au lieu de simplement Function1
. Leur utilisation effective a ensuite suivi, et le sucre syntaxique pour les ceux Les utilisations sont venues plus tard.
L'un d'eux est Limites du contexte utilisé pour mettre en œuvre le modèle de classe de type (pattern car il ne s'agit pas d'une fonctionnalité intégrée, mais simplement d'une façon d'utiliser le langage qui fournit une fonctionnalité similaire à la classe de type de Haskell). Un context bound est utilisé pour fournir un adaptateur qui implémente une fonctionnalité inhérente à une classe, mais non déclarée par celle-ci. Il offre les avantages de l'héritage et des interfaces sans leurs inconvénients. Par exemple :
def max[T](a: T, b: T)(implicit $ev1: Ordering[T]): T = if ($ev1.lt(a, b)) b else a
// latter followed by the syntactic sugar
def max[T: Ordering](a: T, b: T): T = if (implicitly[Ordering[T]].lt(a, b)) b else a
Vous l'avez probablement déjà utilisé. Il y a un cas d'utilisation courant que les gens ne remarquent généralement pas. Il s'agit de ceci :
new Array[Int](size)
Cela utilise un contexte lié à une classe manifeste, pour permettre une telle initialisation de tableau. Nous pouvons le voir avec cet exemple :
def f[T](size: Int) = new Array[T](size) // won't compile!
Vous pouvez l'écrire comme ceci :
def f[T: ClassManifest](size: Int) = new Array[T](size)
Sur la bibliothèque standard, les limites de contexte les plus utilisées sont :
Manifest // Provides reflection on a type
ClassManifest // Provides reflection on a type after erasure
Ordering // Total ordering of elements
Numeric // Basic arithmetic of elements
CanBuildFrom // Collection creation
Les trois dernières sont principalement utilisées avec les collections, avec des méthodes telles que max
, sum
y map
. Scalaz est une bibliothèque qui fait un usage intensif des limites de contexte.
Une autre utilisation courante consiste à réduire le nombre d'opérations qui doivent partager un paramètre commun. Par exemple, les opérations :
def withTransaction(f: Transaction => Unit) = {
val txn = new Transaction
try { f(txn); txn.commit() }
catch { case ex => txn.rollback(); throw ex }
}
withTransaction { txn =>
op1(data)(txn)
op2(data)(txn)
op3(data)(txn)
}
Ce qui est ensuite simplifié comme suit :
withTransaction { implicit txn =>
op1(data)
op2(data)
op3(data)
}
Ce modèle est utilisé avec la mémoire transactionnelle, et je pense (mais je n'en suis pas sûr) que la bibliothèque Scala I/O l'utilise également.
La troisième utilisation courante à laquelle je peux penser est de faire des preuves sur les types qui sont passés, ce qui permet de détecter au moment de la compilation des choses qui, autrement, entraîneraient des exceptions au moment de l'exécution. Par exemple, voir cette définition sur Option
:
def flatten[B](implicit ev: A <:< Option[B]): Option[B]
C'est ce qui rend cela possible :
scala> Option(Option(2)).flatten // compiles
res0: Option[Int] = Some(2)
scala> Option(2).flatten // does not compile!
<console>:8: error: Cannot prove that Int <:< Option[B].
Option(2).flatten // does not compile!
^
Une bibliothèque qui fait un usage intensif de cette fonctionnalité est Shapeless.
Je ne pense pas que l'exemple de la bibliothèque Akka corresponde à l'une de ces quatre catégories, mais c'est là tout l'intérêt des fonctionnalités génériques : les gens peuvent les utiliser de toutes sortes de manières, au lieu des manières prescrites par le concepteur du langage.
Si vous aimez qu'on vous prescrive quelque chose (comme, par exemple, Python), alors Scala n'est pas fait pour vous.
0 votes
Si vous faites un implicite global (et vous ne pouvez pas - le mieux que vous puissiez faire est un implicite à l'échelle du paquet), alors votre déclaration pourrait être vraie, mais seulement si vous choisissez de faire une telle chose... ne le faites pas. Et, en fin de compte, la flexibilité de cette API provient de l'utilisation des implicites. Si vous ne les utilisez pas, vous ne pouvez pas obtenir la même flexibilité. Donc, vous demandez de supprimer la fonctionnalité qui rend cette API géniale, tout en la rendant géniale. C'est une demande très étrange.
0 votes
Derek Wyatt, le dernier commentaire est quelque peu étrange - ne cherchez-vous pas l'optimisation dans la vie ? Je le fais. Maintenant, à propos des variables globales -- je ne dis pas que vous devez avoir des variables globales pour utiliser des implicites, je dis qu'ils sont similaires dans leur utilisation. Parce qu'ils sont liés par le nom de l'appelé, implicitement, et ils sont pris hors de la portée de l'appelant, pas de l'appel réel.