开发者

Why no i++ in Scala?

开发者 https://www.devze.com 2023-02-01 12:28 出处:网络
I just wonder why there is no i++ to increase a number. As what I know, languages like Ruby or Python doesn\'t support it because they are dynamically typed. So it\'s obviously we cannot write code 开

I just wonder why there is no i++ to increase a number. As what I know, languages like Ruby or Python doesn't support it because they are dynamically typed. So it's obviously we cannot write code 开发者_如何学编程like i++ because maybe i is a string or something else. But Scala is statically typed - the compiler absolutely can infer that if it is legal or not to put ++ behind a variable.

So, why doesn't i++ exist in Scala?


Scala doesn't have i++ because it's a functional language, and in functional languages, operations with side effects are avoided (in a purely functional language, no side effects are permitted at all). The side effect of i++ is that i is now 1 larger than it was before. Instead, you should try to use immutable objects (e.g. val not var).

Also, Scala doesn't really need i++ because of the control flow constructs it provides. In Java and others, you need i++ often to construct while and for loops that iterate over arrays. However, in Scala, you can just say what you mean: for(x <- someArray) or someArray.foreach or something along those lines. i++ is useful in imperative programming, but when you get to a higher level, it's rarely necessary (in Python, I've never found myself needing it once).

You're spot on that ++ could be in Scala, but it's not because it's not necessary and would just clog up the syntax. If you really need it, say i += 1, but because Scala calls for programming with immutables and rich control flow more often, you should rarely need to. You certainly could define it yourself, as operators are indeed just methods in Scala.


Of course you can have that in Scala, if you really want:

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

And here you go:

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

No need to resort to violence against variables.


Scala is perfectly capable of parsing i++ and, with a small modification to the language, could be made to modify a variable. But there are a variety of reasons not to.

First, it saves only one character, i++ vs. i+=1, which is not very much savings for adding a new language feature.

Second, the ++ operator is widely used in the collections library, where xs ++ ys takes collection xs and ys and produces a new collection that contains both.

Third, Scala tries to encourage you, without forcing you, to write code in a functional way. i++ is a mutable operation, so it's inconsistent with the idea of Scala to make it especially easy. (Likewise with a language feature that would allow ++ to mutate a variable.)


Scala doesn't have a ++ operator because it is not possible to implement one in it.

EDIT: As just pointed out in response to this answer, Scala 2.10.0 can implement an increment operator through use of macros. See this answer for details, and take everything below as being pre-Scala 2.10.0.

Let me elaborate on this, and I'll rely heavily on Java, since it actually suffers from the same problem, but it might be easier for people to understand it if I use a Java example.

To start, it is important to note that one of the goals of Scala is that the "built-in" classes must not have any capability that could not be duplicated by a library. And, of course, in Scala an Int is a class, whereas in Java an int is a primitive -- a type entirely distinct from a class.

So, for Scala to support i++ for i of type Int, I should be able to create my own class MyInt also supporting the same method. This is one of the driving design goals of Scala.

Now, naturally, Java does not support symbols as method names, so let's just call it incr(). Our intent then is to try to create a method incr() such that y.incr() works just like i++.

Here's a first pass at it:

public class Incrementable {
    private int n;

    public Incrementable(int n) {
        this.n = n;
    }

    public void incr() {
        n++;
    }

    @Override
    public String toString() {
        return "Incrementable("+n+")";
    }
}

We can test it with this:

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

Everything seems to work, too:

Incrementable(0)
Incrementable(1)

And, now, I'll show what the problem is. Let's change our demo program, and make it compare Incrementable to 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);
    }
}

As we can see in the output, Incrementable and int are behaving differently:

i                   j                       k       l
Incrementable(0)    Incrementable(0)        0       0
Incrementable(1)    Incrementable(1)        1       0

The problem is that we implemented incr() by mutating Incrementable, which is not how primitives work. Incrementable needs to be immutable, which means that incr() must produce a new object. Let's do a naive change:

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

However, this doesn't work:

i                   j                       k       l
Incrementable(0)    Incrementable(0)        0       0
Incrementable(0)    Incrementable(0)        1       0

The problem is that, while, incr() created a new object, that new object hasn't been assigned to i. There's no existing mechanism in Java -- or Scala -- that would allow us to implement this method with the exact same semantics as ++.

Now, that doesn't mean it would be impossible for Scala to make such a thing possible. If Scala supported parameter passing by reference (see "call by reference" in this wikipedia article), like C++ does, then we could implement it!

Here's a fictitious implementation, assuming the same by-reference notation as in C++.

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

This would either require JVM support or some serious mechanics on the Scala compiler.

In fact, Scala does something similar to what would be needed that when it create closures -- and one of the consequences is that the original Int becomes boxed, with possibly serious performance impact.

For example, consider this method:

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

The code being passed to foreach, { n => sum += n }, is not part of this method. The method foreach takes an object of the type Function1 whose apply method implements that little code. That means { n => sum += n } is not only on a different method, it is on a different class altogether! And yet, it can change the value of sum just like a ++ operator would need to.

If we use javap to look at it, we'll see this:

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

Note that instead of creating an int local variable, it creates an IntRef on the heap (at 0), which is boxing the int. The real int is inside IntRef.elem, as we see on 25. Let's see this same thing implemented with a while loop to make clear the difference:

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

That becomes:

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

No object creation above, no need to get something from the heap.

So, to conclude, Scala would need additional capabilities to support an increment operator that could be defined by the user, as it avoids giving its own built-in classes capabilities not available to external libraries. One such capability is passing parameters by-reference, but JVM does not provide support for it. Scala does something similar to call by-reference, and to do so it uses boxing, which would seriously impact performance (something that would most likely come up with an increment operator!). In the absence of JVM support, therefore, that isn't much likely.

As an additional note, Scala has a distinct functional slant, privileging immutability and referential transparency over mutability and side effects. The sole purpose of call by-reference is to cause side effects on the caller! While doing so can bring performance advantages in a number of situations, it goes very much against the grain of Scala, so I doubt call by-reference will ever be part of it.


Other answers have already correctly pointed out that a ++ operator is neither particularly useful nor desirable in a functional programming language. I would like to add that since Scala 2.10, you can add a ++ operator, if you want to. Here is how:

You need an implicit macro that converts ints to instances of something that has a ++ method. The ++ method is "written" by the macro, which has access to the variable (as opposed to its value) on which the ++ method is called. Here is the macro implementation:

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
    }
  })
}

Now, as long as the implicit conversion macro is in scope, you can write

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


Rafe's answer is true about the rationale for why something like i++ doesn't belong in Scala. However I have one nitpick. It's actually not possible to implement i++ in Scala without changing the language.

In Scala, ++ is a valid method, and no method implies assignment. Only = can do that.

Languages like C++ and Java treat ++ specially to mean both increment and assign. Scala treats = specially, and in an inconsistent way.

In Scala when you write i += 1 the compiler first looks for a method called += on the Int. It's not there so next it does it's magic on = and tries to compile the line as if it read i = i + 1. If you write i++ then Scala will call the method ++ on i and assign the result to... nothing. Because only = means assignment. You could write i ++= 1 but that kind of defeats the purpose.

The fact that Scala supports method names like += is already controversial and some people think it's operator overloading. They could have added special behavior for ++ but then it would no longer be a valid method name (like =) and it would be one more thing to remember.


Quite a few languages do not support the ++ notation, such as Lua. In languages in which it is supported, it is frequently a source of confusion and bugs, so it's quality as a language feature is dubious, and compared to the alternative of i += 1 or even just i = i + 1, the saving of such minor characters is fairly pointless.

This is not at all relevant to the type system of the language. While it's true that most static type languages do offer and most dynamic types don't, that's a correlation and definitely not a cause.


Scala encourages using of FP style, which i++ certainly is not.


The question to ask is why there should be such an operator, not why there shouldn't be. Would Scala be improved by it?

The ++ operator is single-purpose, and having an operator that can change the value of a variable can cause problems. It's easy to write confusing expressions, and even if the language defines what i = i + i++ means, for example, that's a lot of detailed rules to remember.

Your reasoning on Python and Ruby is wrong, by the way. In Perl, you can write $i++ or ++$i just fine. If $i turns out to be something that can't be incremented, you get a run-time error. It isn't in Python or Ruby because the language designers didn't think it was a good idea, not because they're dynamically typed like Perl.


You could simulate it, though. As a trivial example:

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)

Add some implicit conversions and you're good to go. However, this sort of changes the question into: why isn't there a mutable RichInt with this functionality?


As another answer suggests, the increment operator, as found in i++, was—

supposedly ... added to the B language [a predecessor of the C language] by Ken Thompson specifically because [it was] capable of translating directly to a single opcode once compiled

and not necessarily because such an operator is as useful to have as, say, general addition and subtraction. Although certain object-oriented languages (such as Java and C#) also have an increment operator (often borrowed from C), not all do (such as Ruby).

0

精彩评论

暂无评论...
验证码 换一张
取 消