var
Scala におけると の定義の違いは何ですか?val
また、言語に両方が必要なのはなぜですか? をval
よりも選択する理由var
と、その逆の理由は何ですか?
ベストアンサー1
多くの人が言っているように、 に割り当てられたオブジェクトはval
置き換えることができませんが、 に割り当てられたオブジェクトは置き換えることができますvar
。ただし、そのオブジェクトの内部状態は変更できます。例:
class A(n: Int) {
var value = n
}
class B(n: Int) {
val value = new A(n)
}
object Test {
def main(args: Array[String]) {
val x = new B(5)
x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
x.value.value = 6 // Works, because A.value can receive a new object.
}
}
したがって、 に割り当てられたオブジェクトを変更することはできませんがx
、そのオブジェクトの状態を変更することはできます。ただし、その根本には がありましたvar
。
さて、不変性は多くの理由から良いことです。まず、オブジェクトが内部状態を変更しない場合は、コードの他の部分でそれが変更されているかどうかを心配する必要はありません。たとえば、次のようになります。
x = new B(0)
f(x)
if (x.value.value == 0)
println("f didn't do anything to x")
else
println("f did something to x")
これは、マルチスレッド システムでは特に重要になります。マルチスレッド システムでは、次のことが起こる可能性があります。
x = new B(1)
f(x)
if (x.value.value == 1) {
print(x.value.value) // Can be different than 1!
}
を排他的に使用し、不変のデータ構造のみを使用する場合val
(つまり、配列や 内のすべてのものscala.collection.mutable
などを避ける場合)、このようなことは発生しません。つまり、リフレクション トリックを実行するコード(場合によってはフレームワーク)がない限り、残念ながらリフレクションによって「不変」な値が変更される可能性があります。
それは 1 つの理由ですが、別の理由もあります。 を使用するときvar
、同じものを複数の目的で再利用したくなることがありますvar
。これにはいくつか問題があります。
- コードを読む人にとって、コードの特定の部分にある変数の値が何であるかを知ることはより困難になります。
- 一部のコード パスで変数を再初期化するのを忘れて、コードの下流に間違った値を渡してしまう可能性があります。
簡単に言えば、 を使用するとval
より安全になり、より読みやすいコードになります。
では、逆の方向に進むことができます。val
その方がよいのであれば、なぜそうする必要があるのでしょうvar
か? 実際に、一部の言語ではその方法を採用していますが、可変性によってパフォーマンスが大幅に向上する状況もあります。
たとえば、不変の を取り上げますQueue
。 または を実行するとenqueue
、dequeue
新しいQueue
オブジェクトが生成されます。 では、その中のすべての項目をどのように処理するのでしょうか。
例を使って説明しましょう。数字のキューがあり、それらから数字を構成したいとします。たとえば、2、1、3 の順序のキューがある場合、213 という数字を返したいとします。まず、次のようにしてこれを解決しましょうmutable.Queue
。
def toNum(q: scala.collection.mutable.Queue[Int]) = {
var num = 0
while (!q.isEmpty) {
num *= 10
num += q.dequeue
}
num
}
このコードは高速で理解しやすいです。主な欠点は、渡されるキューが によって変更されるtoNum
ため、事前にコピーを作成する必要があることです。これは、不変性によって解放されるオブジェクト管理の種類です。
さて、これを に変換してみましょうimmutable.Queue
:
def toNum(q: scala.collection.immutable.Queue[Int]) = {
def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
if (qr.isEmpty)
num
else {
val (digit, newQ) = qr.dequeue
recurse(newQ, num * 10 + digit)
}
}
recurse(q, 0)
}
前の例のように、一部の変数を再利用して を追跡することはできないため、num
再帰に頼る必要があります。この場合、末尾再帰はパフォーマンスがかなり良好です。ただし、常にそうであるとは限りません。適切な (読みやすく、シンプルな) 末尾再帰ソリューションが存在しない場合もあります。
immutable.Queue
ただし、そのコードを書き直して、 anと a をvar
同時に使用できることに注意してください。たとえば、次のようになります。
def toNum(q: scala.collection.immutable.Queue[Int]) = {
var qr = q
var num = 0
while (!qr.isEmpty) {
val (digit, newQ) = qr.dequeue
num *= 10
num += digit
qr = newQ
}
num
}
このコードは依然として効率的で、再帰を必要とせず、 を呼び出す前にキューのコピーを作成する必要があるかどうかを心配する必要はありませんtoNum
。当然、他の目的で変数を再利用することは避けており、この関数の外部のコードはそれらを参照しないため、明示的に変更する場合を除いて、行間で値が変化することを心配する必要はありません。
Scala は、プログラマーがそれが最善の解決策だと判断した場合に、プログラマーにそのようにさせることを選択しました。他の言語は、そのようなコードを難しくすることを選択しました。Scala (および広範囲にわたる可変性を持つすべての言語) が支払う代償は、コンパイラーが他の場合ほどコードを最適化する余裕がないことです。Java の答えは、実行時プロファイルに基づいてコードを最適化することです。それぞれの長所と短所については、いくらでも語ることができます。
個人的には、Scalaは今のところ適切なバランスを保っていると思います。完璧というわけではありませんが、クロージュアそしてハスケルScala では採用されていない非常に興味深い概念がありますが、Scala にも独自の強みがあります。将来何が起こるか見てみましょう。