scalaでは、なぜインクリメントやデクリメントができないのか?

javaでは、以下のように

int a = 0;
int b = ++a;
System.out.print(a); // 1 が表示される
System.out.print(b); // bも1

++というインクリメントするための演算子があります。
しかしscalaの場合

var a = 0
// a ++  これはダメ
val b = a += 1
print(a) // 1 が表示される
print(b) // b はUnit

というようにしないといけません。
この違いについてのおなはし(・ω・)ノ
これをちゃんと解説しようとすると、かなりややこしいんですが、それなりの理由があります。
すごく細かいところだけど、Javaとの違いで初心者が戸惑いやすいところだと思います。

Javaの言語仕様書の15.14 〜 15.18あたりだとおもいますが、++、+=、--、-=、などは、すべてOperator(演算子)として説明されてますよね?

しかし、Scalaには演算子は存在せず、すべてがメソッドです。scalaでは、

1 + 2

というのは、

1 .+(2)

とも書けます。
1というオブジェクトの、+というメソッドを呼び出し、その引数として2を渡す
という意味です。
なぜインクリメントの++というものが存在しないかというと、

  • javaでのprimitive型も、scalaでは普通のオブジェクトと統一的に扱う
  • つまり、演算子が存在せず、(IntやCharなどJavaではprimitive型と呼ばれているものも)すべてがオブジェクトであり、すべてがメソッド

という原則を優先したためです。
つまり、iがInt型のとき、++iや、i++という構文を許してしまうと、これらをメソッドを呼び出すという構文で解釈できないからです。

javaの場合のインクリメント

int i = 0;
int b = ++i;

が意味するところは、

  • i という変数の値を1増やす
  • かつ、 ++i という式全体としてもi+1した値を返す

という感じですかね。
しかし、scala的な考え方をすると、

  • var i = 0 というのは、"iという変数に0というIntのオブジェクトをbind(束縛)した" ということになる
  • Intというのは、immutableな(変更不可能で状態を持たない)オブジェクトである
  • よって、iという変数に再代入しない限り、iに束縛された0のメソッドを呼んでも、iは0のままであるはず

ということになります。

ここでいいたいのは、

インクリメントって、一度束縛された変数の値を、代入以外の方法で違うものに書き換えてるよね?しかもprimitive型しかそういう操作できないし。それってなんか統一的じゃなくね?

ってことです。整理すると以下のようになります

Java インクリメントやデクリメントなどの操作を行うことで、変数に違うオブジェクトを束縛しなおすことができる
scala 代入以外の方法では、変数への束縛ができない

ではなぜscala

var i = 0
i += 1

とした場合に、i (という変数に束縛される値)が1になるのか?というと、以下のような操作

var i = 0
i = i + 1

シンタックスシュガーだからです!

これがシンタックスシュガーというのは、コップ本*1323、324ページあたりに解説されています。以下引用


+= メソッドをサポートしていないaを使って a += bというコードを書くと、Scalaはa = a + bと解釈しようとしてみるのだ。

+=メソッドだけでなく、=で終わるあらゆるメソッドに同じ考えが適用される。

ところで、このシンタックスシュガーはコレクションだけでなく、あらゆるタイプの値で機能する。

このような効果は、Javaの+=,-=,*=などの代入演算子と似ているが、Scalaでは=で終わるすべての演算子で可能であり、この効果はより一般的なものとなる。

コップ本にもあるように、scalaでは、メソッド名が=で終わるものは特別扱いされます。それがどういうところで使うと便利なのかっていう詳細は、もう説明つかれたのでコップ本嫁ってことで・・・(´・ω・`)

たとえば、このルールがすべてのモノに適用されるので、(説明のための例で、実用的にはあまり意味がないですが)以下のようなことができます↓*2

scala> case class Test(val a:String){         
     | 
     |   def ++++(b:String) = Test(b)
     | 
     | }
defined class Test

scala> val x = Test("hoge")
x: Test = Test(hoge)

scala> x ++++= "fuga" // xはvalなので再代入できない
<console>:9: error: reassignment to val
       x ++++= "fuga"
         ^
scala> var y = Test("hoge")
y: Test = Test(hoge)

scala> y ++++= "fuga" // y = y.++++("fuga") のシンタックスシュガー

scala> y
res1: Test = Test(fuga) // yが Test(fuga) に置き換わってる

このscalaの考え方がすごくわかりやすいか?っていうとどうなんだろう、ってところはありますが・・・コップ本にあるように、collectionのimmutableなものと、mutableなものの切り替えなんかで有用だったりします。

*1:日本語第1版

*2:最初に載せたコード例が、若干不適切なので変更しました。Testクラスを、immutableにして、++++メソッドは、新しいインスタンスを返すように変更