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


301

Scalaのa varval定義の違いは何ですか?なぜ言語は両方を必要とするのですか?なぜあなたはval以上を選ぶのvarですか?

回答:


331

他の多くの人が言ったように、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.Queuevar同時に!例えば:

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。また、を呼び出す前にキューのコピーを作成する必要があるかどうかを心配する必要はありません。当然、他の目的で変数を再利用することは避け、この関数の外のコードはそれらを認識しないため、明示的にそうしない限り、変数の値が1行から次の行に変化することを心配する必要はありません。

Scalaは、プログラマーがそれを最良のソリューションであると判断した場合に、プログラマーにそれを実行させることを選択しました。他の言語は、そのようなコードを困難にすることを選択しました。Scala(および広範囲の可変性を備えた任意の言語)が支払う代償は、コンパイラーがコードを最適化する際に、そうでない場合ほどの余裕がないことです。これに対するJavaの答えは、実行時プロファイルに基づいてコードを最適化することです。私たちはそれぞれの長所と短所についてどんどん進むことができました。

個人的には、現時点ではScalaは適切なバランスをとっていると思います。完璧ではありません。ClojureHaskellの両方には、Scalaで採用されていない非常に興味深い概念があると思いますが、Scalaにも独自の強みがあります。今後の予定を確認します。


少し遅れますが、... var qr = qのコピーを作成しqますか?

5
@davipsによって参照されるオブジェクトのコピーは作成されませんq。そのオブジェクトへの参照のコピー(ヒープではなくスタック上)を作成します。パフォーマンスに関しては、あなたが言っている「それ」についてもっと明確にする必要があります。
Daniel C. Sobral 2013年

わかりました。あなたの助けといくつかの情報((x::xs).drop(1)正確xsにはの「コピー」ではありませんxs)を使用して、こちらのリンクから理解できました。tnx!

「このコードはまだ効率的です」-それはそうですか?qrは不変のキューなので、式qr.dequeueが呼び出されるたびにが作成されますnew Queue(< github.com/scala/scala/blob/2.13.x/src/library/scala/collection/…を参照)。
オーエン、

@オーウェンはい、しかしそれが浅いオブジェクトであることに注意してください。キューをコピーした場合、コードは変更可能か不変かに関わらず、O(n)のままです。
Daniel C. Sobral

61

val最終的な、つまり設定できません。finalJavaで考えてください。


3
しかし、私が正しく理解していれば(Scalaの専門家ではありません)、val 変数は不変ですが、変数が参照するオブジェクトはそうである必要はありません。Stefanが投稿したリンクによると、「ここでは、名前の参照を別の配列を指すように変更することはできませんが、配列自体は変更できます。つまり、配列の内容/要素は変更できます。」つまりfinal、Javaでの動作のようなものです。
ダニエル・プライデン09年

3
そのまま投稿した理由。私はジャストファイン+=として定義された可変ハッシュマップを呼び出すことができますvalfinal
ジャクソンデイビス

ああ、私は組み込みのscala型が単に再割り当てを許可するよりも良いと思った。事実確認が必要です。
Stefan Kendall

Scalaの不変のシーケンス型を一般的な概念と混同していた。関数型プログラミングのおかげで、私はすべて好転しました。
Stefan Kendall

回答にダミーの文字を追加して削除したので、あなたに賛成票を与えることができます。
Stefan Kendall、


20

違いは、aにvarは再割り当てvalできるのに対し、aには再割り当てできないことです。変更可能性、または実際に割り当てられているものは何でも、副次的な問題です。

import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.

一方:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

それゆえ:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

データ構造を構築していて、そのフィールドがすべてvalsの場合、その状態は変更できないため、そのデータ構造は不変です。


1
これは、それらのフィールドのクラスも不変である場合にのみ当てはまります。
ダニエルC.ソブラル

ええ、それを入れようとしていましたが、一歩行き過ぎかもしれません!それはまた私が言いたい議論の余地のあるポイントです。1つの観点から(機能的なものではありません)、その状態の状態が変化しても、その状態は変化しません。
oxbow_lakes 2009年

JVM言語で不変オブジェクトを作成するのがなぜそれほど難しいのですか?さらに、Scalaはなぜデフォルトでオブジェクトを不変にしないのですか?
Derek Mahar 14


13

C ++の観点から考えると、

val x: T

非定数データへの定数ポインタに類似しています

T* const x;

ながら

var x: T 

非定数データへの非定数ポインタに類似しています

T* x;

有利valオーバーvarと、コードベースの不変性が高まり、コードベースの正確性、並行性、理解しやすさが向上します。

非定数データへの定数ポインターを持つことの意味を理解するには、次のScalaスニペットを検討してください。

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

ここで「ポインタ」 val mは定数なので、別のものを指すように再割り当てすることはできません

m = n // error: reassignment to val

ただし、そのmように指す非定数データ自体を実際に変更できます

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

1
Scalaは最終的な結論である不変のポインター、つまり定数ポインターと定数データを不変にとらえなかったと思います。Scalaは、デフォルトでオブジェクトを不変にする機会を逃しました。その結果、ScalaにはHaskellと同じ価値観がありません。
Derek Mahar、2015年

あなた@DerekMaharしている権利が、オブジェクトは、まだその実装に可変性を利用しながら、完全に不変としての地位を提示することができます例えば、パフォーマンス上の理由から。コンパイラーは、真の可変性と内部のみの可変性をどのように区別できるでしょうか?
francoisr 2018

8

「valは不変を意味し、varは変更可能を意味します。」

言い換えると、「valは値を意味し、varは変数を意味します」。

コンピューティングで非常に重要な違い(これらの2つの概念はプログラミングの本質を明確に定義しているため)であり、OOはほぼ完全にぼやけてきました。OOでは、「すべてがオブジェクト」。そして、その結果として、最近の多くのプログラマーは、「オブジェクト指向の方法を考える」ことだけに洗脳されているため、理解/認識/認識しない傾向があります。多くの場合、あらゆる場所と同じように変数/可変オブジェクトが使用されますが、値/不変オブジェクトの方が優れている場合があります。


これが、例えばJavaよりもHaskellを好む理由です。
デレク・マハール

6

valは不変を意味し、varは変更可能を意味します

valJavaプログラミング言語のfinalキーワールドまたはC ++言語のconstキーワールドと考えることができます。


1

Val最後のを意味し、再割り当てできません

一方、後で再割り当てVarすることができます。


1
この回答は、すでに提出された12の回答とどのように異なりますか?
jwvh 2018

0

名前と同じくらい簡単です。

varは変化する可能性があることを意味します

valは不変を意味します


0

Val-値は型付き記憶定数です。いったん作成されると、その値を再度割り当てることはできません。新しい値はキーワードvalで定義できます。

例えば。val x:Int = 5

scalaは割り当てられた値から推測できるため、ここではタイプはオプションです。

Var-変数は型付きのストレージユニットであり、メモリスペースが予約されている限り、値を再度割り当てることができます。

例えば。var x:Int = 5

両方のストレージユニットに格納されたデータは、不要になるとJVMによって自動的に割り当て解除されます。

安定性のため、スカラーでは変数よりも値が推奨されます。これは、特に並行およびマルチスレッドコードでコードにもたらされます。


0

多くの人がすでにValvarの違いに答えていますが。ただし、注意すべき点は、valはfinalとまったく同じではないということです。キーワードとです。

再帰を使用してvalの値を変更できますが、finalの値を変更することはできません。FinalはValよりも一定です。

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

メソッドパラメータはデフォルトでvalであり、呼び出しごとに値が変更されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.