Scala ne dispose pas d'un ++
car il n'est pas possible d'en implémenter un.
EDIT : Comme il vient d'être signalé en réponse à cette réponse, Scala 2.10.0 puede implémenter un opérateur d'incrémentation en utilisant des macros. Voir cette réponse pour plus de détails, et considérez que tout ce qui suit est antérieur à la version 2.10.0 de Scala.
Permettez-moi de développer ce point. Je m'appuierai largement sur Java, car il souffre en fait du même problème, mais il sera peut-être plus facile pour les gens de le comprendre si j'utilise un exemple Java.
Pour commencer, il est important de noter que l'un des objectifs de Scala est que les classes "intégrées" ne doivent avoir aucune capacité qui ne pourrait pas être dupliquée par une bibliothèque. Et, bien sûr, en Scala, une classe Int
est une classe, alors qu'en Java, une int
est une primitive -- un type entièrement distinct d'une classe.
Donc, pour que Scala supporte i++
para i
de type Int
Je devrais être capable de créer ma propre classe. MyInt
qui soutiennent également la même méthode. C'est l'un des principaux objectifs de conception de Scala.
Naturellement, Java ne prend pas en charge les symboles comme noms de méthode, alors appelons-la simplement incr()
. Notre intention est alors d'essayer de créer une méthode incr()
tal 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 String toString() {
return "Incrementable("+n+")";
}
}
Nous pouvons le tester avec ceci :
public class DemoIncrementable {
static public void main(String[] 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. Changeons notre programme de démonstration, et faisons en sorte qu'il compare Incrementable
a int
:
public class DemoIncrementable {
static public void main(String[] 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
y int
se comportent différemment :
i j k l
Incrementable(0) Incrementable(0) 0 0
Incrementable(1) Incrementable(1) 1 0
Le problème est que nous avons mis en place incr()
en mutant Incrementable
ce qui ne correspond pas au fonctionnement des primitives. Incrementable
doit être immuable, ce qui signifie que incr()
doit produire un nouveau objet. Faisons un changement naïf :
public Incrementable incr() {
return new Incrementable(n + 1);
}
Cependant, cela ne fonctionne pas :
i j k l
Incrementable(0) Incrementable(0) 0 0
Incrementable(0) Incrementable(0) 1 0
Le problème est que, bien que, incr()
créé un nouvel objet, ce nouvel objet n'a pas été assigné a i
. Il n'y a pas de mécanisme existant en Java -- ou en Scala -- qui nous permettrait d'implémenter cette méthode avec exactement la même sémantique que ++
.
Cela ne veut pas dire qu'il serait impossible pour Scala de faire une telle chose possible . Si Scala supportait le passage de paramètres par référence (voir " call by reference " dans le document cet article de wikipedia ), comme le fait C++, alors nous pourrait mettez-la en œuvre !
Voici une mise en œuvre fictive, en supposant que la notation par référence est la même qu'en C++.
implicit def toIncr(Int &n) = {
def ++ = { val tmp = n; n += 1; tmp }
def prefix_++ = { n += 1; n }
}
Cela nécessiterait soit le support de la JVM, soit de sérieuses modifications du compilateur Scala.
En fait, Scala fait quelque chose de similaire à ce qui serait nécessaire pour que lorsqu'il crée des fermetures -- et une des conséquences est que l'original Int
devient encadré, ce qui peut avoir un impact important 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 }
c'est pas de cette méthode. La méthode foreach
prend un objet du type Function1
dont apply
met en œuvre ce petit code. Cela signifie { n => sum += n }
n'est pas seulement sur une méthode différente, il est sur une classe complètement différente ! Et pourtant, il peut changer la valeur de sum
comme un ++
l'opérateur en aurait besoin.
Si nous utilisons javap
pour le regarder, on verra ça :
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."<init>":(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."<init>":(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 un int
variable locale, il crée un IntRef
sur le tas (à 0), qui boxe la int
. Le véritable int
est à l'intérieur IntRef.elem
comme nous le voyons au 25. Voyons la même chose implémentée avec une boucle while pour bien 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'objet ci-dessus, pas besoin d'aller chercher quelque chose dans le tas.
Donc, pour conclure, Scala aurait besoin de capacités supplémentaires pour supporter un opérateur d'incrémentation qui pourrait être défini par l'utilisateur, car il évite de donner à ses propres classes intégrées des capacités qui ne sont pas disponibles pour les bibliothèques externes. L'une de ces capacités est le passage de paramètres par référence, mais la JVM ne la prend pas en charge. Scala fait quelque chose de similaire pour appeler par référence, et pour ce faire, il utilise la mise en boîte, ce qui aurait un impact sérieux sur les performances (quelque chose qui viendrait très probablement avec un opérateur d'incrémentation !) En l'absence de support de la JVM, cela n'est donc pas très probable.
En outre, Scala a un penchant fonctionnel distinct, privilégiant l'immuabilité et la transparence référentielle sur la mutabilité et les effets secondaires. Le site sole le 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 à l'encontre de l'esprit de Scala, et je doute que le call by-reference fasse un jour partie de Scala.