Bon exemple de paramètre implicite dans Scala?

Jusqu’à présent, les parameters implicites dans Scala ne me paraissent pas bons – ils sont trop proches des variables globales, mais comme Scala semble être un langage assez ssortingct, je commence à douter de moi-même :-).

Question: pourriez-vous montrer un exemple concret (ou proche) lorsque les parameters implicites fonctionnent vraiment. IOW: quelque chose de plus sérieux que showPrompt , qui justifierait une telle conception du langage.

Ou, au contraire, pourriez-vous montrer une conception linguistique fiable (peut être imaginaire) qui rendrait implicite non nécessaire. Je pense que même aucun mécanisme ne vaut mieux que les implicites car le code est plus clair et il n’y a pas de devinettes.

Veuillez noter que je pose des questions sur les parameters, pas sur les fonctions implicites (conversions)!

Mises à jour

Variables globales

Merci pour toutes les bonnes réponses. Peut-être que je clarifie mon objection “variables globales”. Considérez une telle fonction:

 max(x : Int,y : Int) : Int 

tu l’appelles

 max(5,6); 

vous pourriez (!) le faire comme ceci:

 max(x:5,y:6); 

mais à mes yeux implicits travaux comme celui-ci:

 x = 5; y = 6; max() 

ce n’est pas très différent d’une telle construction (PHP-like)

 max() : Int { global x : Int; global y : Int; ... } 

La réponse de Derek

C’est un bon exemple, cependant, si vous considérez comme flexible l’utilisation de l’envoi de message sans utiliser implicit , publiez un contre-exemple. Je suis vraiment curieux de la pureté dans la conception du langage ;-).

Dans un sens, oui, les implicites représentent l’état global. Cependant, ils ne sont pas mutables, ce qui est le véritable 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 exigent généralement que vous transformiez toutes les constantes de votre code en constantes ou enums, généralement globales.

Notez également que les implicits ne sont pas dans un espace de noms plat, ce qui est également un problème courant avec les globals. Ils sont explicitement liés aux types et, par conséquent, à la hiérarchie des packages de ces types.

Alors, prenez vos globales, rendez-les immuables et initialisées sur le site de déclaration, et placez-les sur des espaces de noms. Est-ce qu’ils ressemblent toujours à des globals? Est-ce qu’ils ont toujours l’air problématique?

Mais ne nous arrêtons pas là. Les implicits sont liés aux types et ils sont tout aussi “globaux” que les types. Est-ce que le fait que les types soient globaux vous dérange?

En ce qui concerne les cas d’utilisation, ils sont nombreux, mais nous pouvons faire une brève revue en fonction de leur historique. À l’origine, Scala n’avait pas d’implicites. Ce que Scala avait, c’était des types de vues, une caractéristique que beaucoup d’autres langues avaient. Nous pouvons encore le voir aujourd’hui chaque fois que vous écrivez quelque chose comme T <% Ordered[T] , ce qui signifie que le type T peut être vu comme un type Ordered[T] . Les types de vue sont un moyen de rendre des dissortingbutions automatiques disponibles sur les parameters de type (génériques).

Scala a ensuite généralisé cette fonctionnalité avec des implicits. Les dissortingbutions automatiques n'existent plus et, au lieu de cela, vous avez des conversions implicites - qui ne sont que des valeurs Function1 et, par conséquent, peuvent être transmises en tant que parameters. A partir de là, T <% Ordered[T] signifiait qu'une valeur pour une conversion implicite serait transmise en paramètre. Comme la conversion est automatique, l'appelant de la fonction n'est pas obligé de passer explicitement le paramètre - de sorte que ces parameters sont devenus des parameters implicites .

Notez qu'il existe deux concepts - les conversions implicites et les parameters implicites - qui sont très proches, mais ne se recouvrent pas complètement.

Quoi qu'il en soit, les types de vue sont devenus du 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 parameters implicites sont simplement une généralisation de ce modèle, ce qui permet de passer tout type de parameters implicites, au lieu de simplement Function1 . Leur utilisation réelle suivait alors, et le sucre syntaxique pour ces utilisations venait ensuite.

L'un d'eux est Context Bounds , utilisé pour implémenter le modèle de classe de type (pattern car ce n'est pas une fonctionnalité intégrée, mais simplement un moyen d'utiliser le langage qui offre des fonctionnalités similaires à la classe de type Haskell). Un lien contextuel est utilisé pour fournir un adaptateur qui implémente des fonctionnalités inhérentes à une classe, mais non déclarées par celle-ci. Il offre les avantages de l'inheritance 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. C'est ça:

 new Array[Int](size) 

Cela utilise un contexte lié à une classe manifeste, pour permettre une telle initialisation du tableau. Nous pouvons voir cela avec cet exemple:

 def f[T](size: Int) = new Array[T](size) // won't comstack! 

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 les suivantes:

 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 derniers sont principalement utilisés avec des collections, avec des méthodes telles que max , sum et map . Une bibliothèque utilisant largement les limites de contexte est Scalaz.

Une autre utilisation courante est de diminuer la plaque chauffante sur les opérations qui doivent partager un paramètre commun. Par exemple, les transactions:

 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 alors simplifié comme ceci:

 withTransaction { implicit txn => op1(data) op2(data) op3(data) } 

Ce modèle est utilisé avec la mémoire transactionnelle et je pense (mais je ne suis pas sûr) que la bibliothèque d'E / S Scala l'utilise également.

Le troisième usage courant auquel je peux penser consiste à faire des preuves sur les types qui sont passés, ce qui permet de détecter à la compilation des éléments qui, autrement, entraîneraient des exceptions d'exécution. Par exemple, voir cette définition sur Option :

 def flatten[B](implicit ev: A <:< Option[B]): Option[B] 

Cela rend cela possible:

 scala> Option(Option(2)).flatten // comstacks res0: Option[Int] = Some(2) scala> Option(2).flatten // does not comstack! :8: error: Cannot prove that Int <:< Option[B]. Option(2).flatten // does not compile! ^ 

Une bibliothèque qui utilise largement cette fonctionnalité est Shapeless.

Je ne pense pas que l’exemple de la bibliothèque Akka corresponde à l’une ou l’autre de ces quatre catégories, mais c’est là l’intérêt des fonctionnalités génériques: les gens peuvent l’utiliser de toutes les manières, au lieu des méthodes prescrites par le concepteur linguistique.

Si vous aimez être prescrit (comme, disons, Python le fait), Scala n'est tout simplement pas pour vous.

Sûr. Akka en a un excellent exemple en ce qui concerne ses acteurs. Lorsque vous êtes dans la méthode de receive un acteur, vous pouvez envoyer un message à un autre acteur. Lorsque vous faites cela, Akka va regrouper (par défaut) l’acteur actuel en tant sender du message, comme ceci:

 trait ScalaActorRef { this: ActorRef => ... def !(message: Any)(implicit sender: ActorRef = null): Unit ... } 

L’ sender est implicite. Dans l’acteur, il y a une définition qui ressemble à:

 trait Actor { ... implicit val self = context.self ... } 

Cela crée la valeur implicite dans la scope de votre propre code et vous permet de faire des choses simples comme ceci:

 someOtherActor ! SomeMessage 

Maintenant, vous pouvez le faire aussi, si vous aimez:

 someOtherActor.!(SomeMessage)(self) 

ou

 someOtherActor.!(SomeMessage)(null) 

ou

 someOtherActor.!(SomeMessage)(anotherActorAltogether) 

Mais normalement vous ne le faites pas. Vous conservez simplement l’utilisation naturelle rendue possible par la définition de valeur implicite dans le trait Acteur. Il y a environ un million d’autres exemples. Les classes de collection sont énormes. Essayez d’errer dans une bibliothèque Scala non sortingviale et vous trouverez un camion.

Un exemple serait les opérations de comparaison sur Traversable[A] . Par exemple max ou sort :

 def max[B >: A](implicit cmp: Ordering[B]) : A 

Celles-ci ne peuvent être définies de manière sensible que s’il y a une opération < on A Donc, sans implication, nous devrions fournir le contexte Ordering[B] chaque fois que nous aimerions utiliser cette fonction. (Ou renoncez à une vérification statique de type à l'intérieur de max et risquez une erreur de conversion à l'exécution.)

Si, toutefois, une classe de type de comparaison implicite est dans la scope, par exemple certaines Ordering[Int] , nous pouvons simplement l'utiliser tout de suite ou simplement changer la méthode de comparaison en fournissant une autre valeur pour le paramètre implicite.

Bien entendu, les implicites peuvent être occultés et il peut donc y avoir des situations dans lesquelles l'implicite réel qui est dans la scope n'est pas suffisamment clair. Pour des utilisations simples de max ou de sort il pourrait en effet suffire d'avoir un trait sort fixe sur Int et d'utiliser une syntaxe pour vérifier si ce trait est disponible. Mais cela signifierait qu'il ne pourrait y avoir aucun trait supplémentaire et que chaque morceau de code devrait utiliser les traits définis à l'origine.

Une addition:
Réponse à la comparaison des variables globales .

Je pense que vous avez raison dans un code comme

 implicit val num = 2 implicit val item = "Orange" def shopping(implicit num: Int, item: Ssortingng) = { "I'm buying "+num+" "+item+(if(num==1) "." else "s.") } scala> shopping res: java.lang.Ssortingng = I'm buying 2 Oranges. 

il peut sentir des variables globales pourries et mauvaises. Le point crucial, cependant, est qu'il ne peut y avoir qu'une seule variable implicite par type dans la scope. Votre exemple avec deux Int ne va pas fonctionner.

En outre, cela signifie que, dans la pratique, les variables implicites ne sont utilisées que lorsqu'il existe une instance primaire non nécessairement unique mais distincte pour un type. La référence de self d'un acteur est un bon exemple pour une telle chose. L'exemple de classe de type est un autre exemple. Il peut y avoir des dizaines de comparaisons algébriques pour tout type mais il y en a une qui est spéciale. (A un autre niveau, le numéro de ligne réel dans le code lui-même peut également constituer une bonne variable implicite, à condition qu'il utilise un type très distinctif.)

Vous n'utilisez normalement pas d' implicit pour les types courants. Et avec les types spécialisés (comme Ordering[Int] ), il n'y a pas trop de risques à les observer.

Une autre bonne utilisation générale des parameters implicites est de faire en sorte que le type de retour d’une méthode dépend du type de certains parameters transmis. Un bon exemple, mentionné par Jens, est le cadre des collections et les méthodes telles que la map , dont la signature complète est généralement:

 def map[B, That](f: (A) ⇒ B)(implicit bf: CanBuildFrom[GenSeq[A], B, That]): That 

Notez que le type de retour That est déterminé par le meilleur ajustement CanBuildFrom que le compilateur peut trouver.

Pour un autre exemple, voyez cette réponse . Là, le type de retour de la méthode Arithmetic.apply est déterminé selon un certain type de paramètre implicite ( BiConverter ).

C’est facile, rappelez-vous simplement:

  • déclarer la variable à transmettre aussi implicite
  • déclarer tous les parameters implicites après les parameters non implicites dans un fichier séparé ()

par exemple

 def myFunction(): Int = { implicit val y: Int = 33 implicit val z: Double = 3.3 functionWithImplicit("foo") // calls functionWithImplicit("foo")(y, z) } def functionWithImplicit(foo: Ssortingng)(implicit x: Int, d: Double) = // blar blar 

D’après mon expérience, il n’y a pas vraiment de bon exemple d’utilisation des parameters implicites ou de la conversion implicite.

Le petit avantage de l’utilisation d’implicits (ne pas avoir besoin d’écrire explicitement un paramètre ou un type) est redondant par rapport aux problèmes qu’ils créent.

Je suis développeur depuis 15 ans et je travaille avec scala depuis 1,5 ans.

J’ai vu plusieurs fois des bugs causés par le développeur qui ignorait le fait que des implicits étaient utilisés et qu’une fonction spécifique renvoyait un type différent de celui spécifié. En raison de la conversion implicite.

J’ai aussi entendu des déclarations disant que si vous n’aimez pas les implicites, ne les utilisez pas. Ce n’est pas pratique dans le monde réel car plusieurs fois les bibliothèques externes sont utilisées, et beaucoup utilisent des implicits, donc votre code utilisant des implicits, et vous pourriez ne pas être au courant de cela. Vous pouvez écrire un code qui a soit:

 import org.some.common.library.{TypeA, TypeB} 

ou:

 import org.some.common.library._ 

Les deux codes seront compilés et exécutés. Mais ils ne produiront pas toujours les mêmes résultats car la seconde version importe la conversion qui fera que le code se comporte différemment.

Le “bug” qui en résulte peut survenir très longtemps après l’écriture du code, au cas où certaines valeurs affectées par cette conversion ne seraient pas utilisées à l’origine.

Une fois que vous rencontrez le bug, sa tâche n’est pas facile à trouver. Vous devez faire des recherches approfondies.

Même si vous vous sentez comme un expert en scala une fois que vous avez trouvé le bogue et que vous l’avez corrigé en modifiant une instruction d’importation, vous avez perdu beaucoup de temps précieux.

Les raisons supplémentaires pour lesquelles je suis généralement contre les implicites sont les suivantes:

  • Ils rendent le code difficile à comprendre (il y a moins de code, mais vous ne savez pas ce qu’il fait)
  • Temps de compilation Le code scala se comstack beaucoup plus lentement lorsque des implicits sont utilisés.
  • En pratique, cela change la langue du type statiquement typé au type dynamic. Il est vrai qu’une fois que vous suivez des directives de codage très ssortingctes, vous pouvez éviter de telles situations, mais dans la réalité, ce n’est pas toujours le cas. Même en utilisant l’IDE «supprimer les importations non utilisées», votre code peut encore être compilé et exécuté, mais pas comme avant la suppression des importations «inutilisées».

Il n’y a pas d’option pour comstackr scala sans implicits (s’il y en a, s’il vous plait corrigez-moi), et s’il y avait une option, aucune des librairies de scala communes à la communauté n’aurait été compilée.

Pour toutes les raisons ci-dessus, je pense que les implicits sont l’une des pires pratiques que le langage Scala utilise.

Scala a beaucoup de fonctionnalités intéressantes, et beaucoup moins géniales.

Lors du choix d’une langue pour un nouveau projet, les implicits sont l’une des raisons de la scala, et non en faveur de celle-ci. À mon avis.

Les parameters implicites sont largement utilisés dans l’API de collecte. De nombreuses fonctions reçoivent un CanBuildFrom implicite, ce qui garantit que vous obtenez la «meilleure» implémentation de la collection de résultats.

Sans implication, vous devriez soit passer une telle chose tout le temps, ce qui rendrait l’utilisation normale lourde. Ou utilisez des collections moins spécialisées, ce qui serait ennuyeux car cela signifierait que vous perdez en performance / puissance.

Je commente ce post un peu tard, mais j’ai commencé à apprendre le scala récemment. Daniel et d’autres ont donné un joli fond sur le mot clé implicite. Je me donnerais deux cents sur la variable implicite du sharepoint vue de l’utilisation pratique.

Scala est le mieux adapté pour écrire des codes Apache Spark. Dans Spark, nous avons un contexte d’allumage et plus probablement la classe de configuration qui peut extraire les clés / valeurs de configuration d’un fichier de configuration.

Maintenant, si j’ai une classe abstraite et si je déclare un object de configuration et un contexte comme suit: –

 abstract class myImplicitClass { implicit val config = new myConfigClass() val conf = new SparkConf().setMaster().setAppName() implicit val sc = new SparkContext(conf) def overrideThisMethod(implicit sc: SparkContext, config: Config) : Unit } class MyClass extends myImplicitClass { override def overrideThisMethod(implicit sc: SparkContext, config: Config){ /*I can provide here n number of methods where I can pass the sc and config objects, what are implicit*/ def firstFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ /*I can use "sc" and "config" as I wish: making rdd or getting data from cassandra, for eg*/ val myRdd = sc.parallelize(List("abc","123")) } def secondFn(firstParam: Int) (implicit sc: SparkContext, config: Config){ /*following are the ways we can use "sc" and "config" */ val keyspace = config.getSsortingng("keyspace") val tableName = config.getSsortingng("table") val hostName = config.getSsortingng("host") val userName = config.getSsortingng("username") val pswd = config.getSsortingng("password") implicit val cassandraConnectorObj = CassandraConnector(....) val cassandraRdd = sc.cassandraTable(keyspace, tableName) } } } 

Comme nous pouvons voir le code ci-dessus, j’ai deux objects implicites dans ma classe abstraite, et j’ai passé ces deux variables implicites en tant que parameters implicites fonction / méthode / définition. Je pense que c’est le meilleur cas d’utilisation que nous pouvons décrire en termes d’utilisation de variables implicites.