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