44 votes

Pourquoi pas de i++ en Scala ?

Je me demande juste pourquoi il n'y a pas i++ pour augmenter un nombre. Pour autant que je sache, des langages comme Ruby ou Python ne le supportent pas car ils sont typés dynamiquement. Il est donc évident que nous ne pouvons pas écrire du code comme i++ parce que peut-être i est une chaîne ou autre chose. Mais Scala est typée statiquement - le compilateur peut absolument déduire que s'il est légal ou non de mettre ++ derrière une variable.

Alors, pourquoi ne pas i++ existent en Scala ?

72voto

Rafe Kettler Points 29389

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

De plus, Scala n'a pas vraiment besoin de i++ en raison des constructions de flux de contrôle qu'il fournit. En Java et dans d'autres langages, vous devez i++ souvent pour construire while y for des boucles qui itèrent sur des tableaux. Cependant, en Scala, vous pouvez simplement dire ce que vous voulez dire : for(x <- someArray) o someArray.foreach ou quelque chose de ce genre. i++ est utile en programmation impérative, mais lorsque vous passez à un niveau supérieur, il est rarement nécessaire (en Python, je n'en ai jamais eu besoin une seule fois).

Vous avez raison. ++ pourrait en Scala, mais il ne l'est pas car il n'est pas nécessaire et ne ferait qu'encombrer la syntaxe. Si vous en avez vraiment besoin, disons i += 1 Mais comme Scala préconise plus souvent la programmation avec des immutables et un flux de contrôle riche, vous devriez rarement en avoir besoin. Vous pourriez certainement le définir vous-même, car les opérateurs sont en effet de simples méthodes en Scala.

35voto

Apocalisp Points 22526

Bien sûr, vous pouvez avoir cela en Scala, si vous le 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.

14voto

Rex Kerr Points 94401

Scala est parfaitement capable d'analyser i++ et, avec une petite modification du langage, on pourrait faire en sorte qu'il modifie une variable. Mais il y a de nombreuses raisons de ne pas le faire.

Tout d'abord, il ne permet de sauvegarder qu'un seul caractère, i++ vs. i+=1 ce qui ne représente pas une économie considérable pour l'ajout d'une nouvelle fonctionnalité linguistique.

Deuxièmement, le ++ L'opérateur est largement utilisé dans la bibliothèque des collections, où xs ++ ys prend la collecte xs y ys et produit une nouvelle collection qui contient 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, il n'est donc pas cohérent avec l'idée de Scala de la rendre particulièrement facile (de même qu'une fonctionnalité du langage qui permettrait à la fonction ++ pour muter une variable).

12voto

Daniel C. Sobral Points 159554

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.

9voto

Kim Stebel Points 22873

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

Vous avez besoin d'une macro implicite qui convertit les ints en instances de quelque chose qui a une valeur de ++ méthode. Le site ++ est "écrite" par la macro, qui a accès à 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 en vigueur, vous pouvez écrire

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X