Pourquoi pas i ++ en Scala?

Je me demande juste pourquoi il n’y a pas i++ pour augmenter un nombre. Comme je le sais, les langages comme Ruby ou Python ne le supportent pas car ils sont typés dynamicment. Donc, évidemment, nous ne pouvons pas écrire du code comme i++ car peut-être que i une chaîne ou autre chose. Mais Scala est statiquement typé – le compilateur peut absolument déduire que si c’est légal ou non de mettre ++ derrière une variable.

Alors, pourquoi i++ n’existe-t-il pas dans Scala?

Scala n’a pas i++ parce que c’est un langage fonctionnel, et dans les langages fonctionnels, les opérations avec effets secondaires sont évitées (dans un langage purement fonctionnel, aucun effet secondaire n’est autorisé). L’effet secondaire de i++ est que i maintenant 1 plus grand que c’était avant. Au lieu de cela, vous devriez essayer d’utiliser des objects immuables (par exemple, val pas var ).

En outre, Scala n’a pas vraiment besoin d’ i++ cause des structures de stream de contrôle qu’il fournit. Dans Java et d’autres, vous avez souvent besoin d’ i++ pour construire for boucles tant pour les itérations que pour les tableaux. Cependant, dans Scala, vous pouvez simplement dire ce que vous voulez dire: for(x <- someArray) ou someArray.foreach ou quelque chose du someArray.foreach . i++ est utile dans la programmation impérative, mais lorsque vous atteignez un niveau supérieur, il est rarement nécessaire (en Python, je ne me suis jamais trouvé en avoir besoin une fois).

Vous êtes au courant que ++ pourrait être dans Scala, mais ce n'est pas parce que ce n'est pas nécessaire et que cela ne ferait que boucher la syntaxe. Si vous en avez vraiment besoin, dites i += 1 , mais comme Scala demande une programmation avec des immuables et un stream de contrôle riche plus souvent, vous devriez rarement en avoir besoin. Vous pouvez certainement le définir vous-même, car les opérateurs ne sont en fait que des méthodes en Scala.

Bien sûr, vous pouvez avoir ça à Scala, si vous voulez vraiment:

 import scalaz._, Scalaz._ case class IncLens[S,N](lens: Lens[S,N], num: Numeric[N]) { def ++ = lens.mods(num.plus(_, num.one)) } implicit def incLens[S,N: Numeric](lens: Lens[S,N]) = IncLens[S,N](lens, implicitly[Numeric[N]]) val i = Lens.lensu[Int,Int]((x, y) => y, identity) val imperativeProgram = for { _ <- i++; _ <- i++; x <- i++ } yield x def runProgram = imperativeProgram exec 0 

Et voilà:

 scala> runProgram res26: scalaz.Id.Id[Int] = 3 

Pas besoin de recourir à la violence contre les variables.

Scala est parfaitement capable d’parsingr i++ et, avec une petite modification du langage, de modifier une variable. Mais il y a une variété de raisons de ne pas le faire.

Premièrement, cela permet d’économiser un seul caractère, i++ vs. i+=1 , ce qui ne représente pas beaucoup d’économies pour l’ajout d’une nouvelle fonctionnalité de langue.

Deuxièmement, l’opérateur ++ est largement utilisé dans la bibliothèque de collections, où xs ++ ys prend la collection xs et ys et produit une nouvelle collection contenant les deux.

Troisièmement, Scala essaie de vous encourager, sans vous forcer, à écrire du code de manière fonctionnelle. i++ est une opération mutable, ce qui est incompatible avec l’idée de Scala pour la rendre particulièrement facile. (De même avec une fonctionnalité de langage qui permettrait à ++ de muter une variable.)

Scala n’a pas d’opérateur ++ car il n’est pas possible d’en implémenter un.

EDIT : Comme nous venons de le dire en réponse à cette réponse, Scala 2.10.0 peut implémenter un opérateur d’incrément via l’utilisation de macros. Voyez cette réponse pour plus de détails, et prenez tout ce qui suit comme étant pré-Scala 2.10.0.

Permettez-moi de vous en dire plus, et je compte beaucoup sur Java, car il souffre du même problème, mais il serait peut-être plus facile pour les gens de comprendre si j’utilise un exemple Java.

Pour commencer, il est important de noter que l’un des objectives de Scala est que les classes “intégrées” ne doivent avoir aucune capacité qui ne puisse être dupliquée par une bibliothèque. Et, bien sûr, dans Scala an Int est une classe, tandis qu’en Java, un int est une primitive – un type entièrement distinct d’une classe.

Donc, pour que Scala supporte i++ pour i de type Int , je devrais pouvoir créer ma propre classe MyInt prenant également en charge la même méthode. C’est l’un des objectives de design de Scala.

Maintenant, naturellement, Java ne prend pas en charge les symboles en tant que noms de méthodes, alors appelons-le simplement incr() . Notre intention est alors d’essayer de créer une méthode incr() telle que y.incr() fonctionne comme i++ .

Voici un premier passage:

 public class Incrementable { private int n; public Incrementable(int n) { this.n = n; } public void incr() { n++; } @Override public Ssortingng toSsortingng() { return "Incrementable("+n+")"; } } 

Nous pouvons le tester avec ceci:

 public class DemoIncrementable { static public void main(Ssortingng[] args) { Incrementable i = new Incrementable(0); System.out.println(i); i.incr(); System.out.println(i); } } 

Tout semble fonctionner aussi:

 Incrementable(0) Incrementable(1) 

Et maintenant, je vais montrer quel est le problème. Modifions notre programme de démonstration et le comparons à Incrementable to int :

 public class DemoIncrementable { static public void main(Ssortingng[] args) { Incrementable i = new Incrementable(0); Incrementable j = i; int k = 0; int l = 0; System.out.println("i\t\tj\t\tk\tl"); System.out.println(i+"\t"+j+"\t"+k+"\t"+l); i.incr(); k++; System.out.println(i+"\t"+j+"\t"+k+"\t"+l); } } 

Comme on peut le voir dans la sortie, Incrementable et int se comportent différemment:

 ijkl Incrementable(0) Incrementable(0) 0 0 Incrementable(1) Incrementable(1) 1 0 

Le problème est que nous avons implémenté incr() en mutant Incrementable , ce qui n’est pas le cas pour les primitives. Incrementable doit être immuable, ce qui signifie que incr() doit produire un nouvel object. Faisons un changement naïf:

 public Incrementable incr() { return new Incrementable(n + 1); } 

Cependant, cela ne fonctionne pas:

 ijkl Incrementable(0) Incrementable(0) 0 0 Incrementable(0) Incrementable(0) 1 0 

Le problème est que, alors que incr() créé un nouvel object, ce nouvel object n’a pas été affecté à i . Il n’y a pas de mécanisme existant en Java – ou Scala – qui nous permettrait d’implémenter cette méthode avec exactement la même sémantique que ++ .

Maintenant, cela ne signifie pas qu’il serait impossible pour Scala de rendre une telle chose possible . Si Scala supportait le passage de parameters par référence (voir “appel par référence” dans cet article de Wikipedia ), comme le fait C ++, alors nous pourrions l’ implémenter!

Voici une implémentation fictive, supposant la même notation de référence que C ++.

 implicit def toIncr(Int &n) = { def ++ = { val tmp = n; n += 1; tmp } def prefix_++ = { n += 1; n } } 

Cela nécessiterait soit un support JVM, soit des mécanismes sérieux sur le compilateur Scala.

En fait, Scala fait quelque chose de similaire à ce qui serait nécessaire quand il crée des fermetures – et l’une des conséquences est que l’ Int original est encaissé, avec un impact potentiellement sérieux sur les performances.

Par exemple, considérez cette méthode:

  def f(l: List[Int]): Int = { var sum = 0 l foreach { n => sum += n } sum } 

Le code transmis à foreach , { n => sum += n } , ne fait pas partie de cette méthode. La méthode foreach prend un object du type Function1 dont la méthode apply implémente ce petit code. Cela signifie que { n => sum += n } n’est pas seulement sur une méthode différente, mais sur une classe complètement différente! Et pourtant, il peut changer la valeur de sum comme le ferait un opérateur ++ .

Si nous utilisons javap pour le regarder, nous verrons ceci:

 public int f(scala.collection.immutable.List); Code: 0: new #7; //class scala/runtime/IntRef 3: dup 4: iconst_0 5: invokespecial #12; //Method scala/runtime/IntRef."":(I)V 8: astore_2 9: aload_1 10: new #14; //class tst$$anonfun$f$1 13: dup 14: aload_0 15: aload_2 16: invokespecial #17; //Method tst$$anonfun$f$1."":(Ltst;Lscala/runtime/IntRef;)V 19: invokeinterface #23, 2; //InterfaceMethod scala/collection/LinearSeqOptimized.foreach:(Lscala/Function1;)V 24: aload_2 25: getfield #27; //Field scala/runtime/IntRef.elem:I 28: ireturn 

Notez qu’au lieu de créer une variable int locale, il crée un IntRef sur le tas (à 0), ce qui correspond à la valeur int . Le vrai int est dans IntRef.elem , comme on le voit sur 25. Voyons cette même chose implémentée avec une boucle while pour faire la différence:

  def f(l: List[Int]): Int = { var sum = 0 var next = l while (next.nonEmpty) { sum += next.head next = next.tail } sum } 

Cela devient:

 public int f(scala.collection.immutable.List); Code: 0: iconst_0 1: istore_2 2: aload_1 3: astore_3 4: aload_3 5: invokeinterface #12, 1; //InterfaceMethod scala/collection/TraversableOnce.nonEmpty:()Z 10: ifeq 38 13: iload_2 14: aload_3 15: invokeinterface #18, 1; //InterfaceMethod scala/collection/IterableLike.head:()Ljava/lang/Object; 20: invokestatic #24; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 23: iadd 24: istore_2 25: aload_3 26: invokeinterface #29, 1; //InterfaceMethod scala/collection/TraversableLike.tail:()Ljava/lang/Object; 31: checkcast #31; //class scala/collection/immutable/List 34: astore_3 35: goto 4 38: iload_2 39: ireturn 

Pas de création d’object ci-dessus, pas besoin d’obtenir quelque chose du tas.

Donc, pour conclure, Scala aurait besoin de capacités supplémentaires pour prendre en charge un opérateur d’incrément qui pourrait être défini par l’utilisateur, car cela évite de donner ses propres capacités de classes intégrées non disponibles aux bibliothèques externes. Une de ces fonctionnalités consiste à transmettre des parameters par référence, mais JVM ne les fournit pas. Scala fait quelque chose de similaire à l’appel par référence, et pour ce faire, il utilise la boxe, ce qui aurait un impact sérieux sur les performances (quelque chose qui reviendrait très probablement avec un opérateur d’incrémentation!). Par conséquent, en l’absence de support JVM, cela n’est pas très probable.

En outre, Scala possède une inclinaison fonctionnelle distincte, privilégiant l’immutabilité et la transparence référentielle par rapport à la mutabilité et aux effets secondaires. Le seul but de l’appel par référence est de provoquer des effets secondaires sur l’appelant ! Bien que cela puisse apporter des avantages en termes de performances dans un certain nombre de situations, cela va très à l’encontre de Scala, alors je doute que l’appel par référence en fasse partie.

D’autres réponses ont déjà correctement indiqué qu’un opérateur ++ n’est ni particulièrement utile ni souhaitable dans un langage de programmation fonctionnel. J’aimerais append que depuis Scala 2.10, vous pouvez append un opérateur ++ , si vous le souhaitez. Voici comment:

Vous avez besoin d’une macro implicite qui convertit ints en instances de quelque chose qui a une méthode ++ . La méthode ++ est “écrite” par la macro, qui a access à la variable (par opposition à sa valeur) sur laquelle la méthode ++ est appelée. Voici l’implémentation de la macro:

 trait Incrementer { def ++ : Int } implicit def withPp(i:Int):Incrementer = macro withPpImpl def withPpImpl(c:Context)(i:c.Expr[Int]):c.Expr[Incrementer] = { import c.universe._ val id = i.tree val f = c.Expr[()=>Unit](Function( List(), Assign( id, Apply( Select( id, newTermName("$plus") ), List( Literal(Constant(1)) ) ) ) )) reify(new Incrementer { def ++ = { val res = i.splice f.splice.apply res } }) } 

Maintenant, tant que la macro de conversion implicite est dans la scope, vous pouvez écrire

 var i = 0 println(i++) //prints 0 println(i) //prints 1 

La réponse de Rafe est vraie quant à la raison pour laquelle quelque chose comme i ++ n’appartient pas à Scala. Cependant, j’ai un nitpick. Il n’est en fait pas possible d’implémenter i ++ dans Scala sans changer la langue.

Dans Scala, ++ est une méthode valide et aucune méthode n’implique l’affectation. Seulement = peut faire ça.

Les langages comme C ++ et Java traitent spécialement ++ pour signifier à la fois l’incrémentation et l’affectation. Scala traite spécialement et de manière incohérente.

En Scala, lorsque vous écrivez i += 1 le compilateur cherche d’abord une méthode appelée += sur l’Int. Ce n’est pas là donc la prochaine fois, c’est magique sur = et essaie de comstackr la ligne comme si elle lisait i = i + 1 . Si vous écrivez i++ Scala appelle la méthode ++ sur i et atsortingbue le résultat à … rien. Parce que seul = signifie assignation. Vous pouvez écrire i ++= 1 mais cela va à l’encontre du but recherché.

Le fait que Scala prenne en charge les noms de méthodes comme += est déjà controversé et certaines personnes pensent qu’il s’agit d’une surcharge d’opérateurs. Ils auraient pu append un comportement spécial pour ++ mais alors ce ne serait plus un nom de méthode valide (comme = ) et ce serait une chose de plus à retenir.

Quelques langages ne supportent pas la notation ++, comme Lua. Dans les langues dans lesquelles il est pris en charge, il est souvent source de confusion et de bogues, de sorte que la qualité en tant que fonctionnalité du langage est douteuse et comparée à l’alternative de i += 1 ou même simplement i = i + 1 ces caractères mineurs sont assez inutiles.

Ce n’est pas du tout pertinent pour le système de type de la langue. S’il est vrai que la plupart des langages de type statique offrent la plupart des types dynamics, ce n’est pas une cause.

Scala encourage l’utilisation du style FP, ce que i++ n’est certainement pas.

La question à se poser est de savoir pourquoi il devrait y avoir un tel opérateur, pas pourquoi il ne devrait pas y en avoir. Scala serait-il amélioré par cela?

L’opérateur ++ est à usage unique et le fait d’avoir un opérateur capable de modifier la valeur d’une variable peut causer des problèmes. Il est facile d’écrire des expressions confuses, et même si le langage définit ce que signifie i = i + i++ , par exemple, il y a beaucoup de règles détaillées à retenir.

Votre raisonnement sur Python et Ruby est faux, en passant. En Perl, vous pouvez écrire $i++ ou ++$i . Si $i s’avère être quelque chose qui ne peut pas être incrémenté, vous obtenez une erreur d’exécution. Ce n’est pas en Python ou Ruby car les concepteurs de langage ne pensaient pas que c’était une bonne idée, pas parce qu’ils étaient tapés dynamicment comme Perl.

Vous pouvez le simuler, cependant. Comme un exemple sortingvial:

 scala> case class IncInt(var self: Int = 0) { def ++ { self += 1 } } defined class IncInt scala> val i = IncInt() i: IncInt = IncInt(0) scala> i++ scala> i++ scala> i res28: IncInt = IncInt(2) 

Ajoutez des conversions implicites et vous êtes prêt à partir. Cependant, cette sorte de changement de la question dans: pourquoi n’y at-il pas un RichInt mutable avec cette fonctionnalité?