Scala の var と val の定義の違いは何ですか? 質問する

Scala の var と val の定義の違いは何ですか? 質問する

varScala におけると の定義の違いは何ですか?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。 または を実行するとenqueuedequeue新しい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 にも独自の強みがあります。将来何が起こるか見てみましょう。

おすすめ記事