Scalaのval-mutableとvar-immutableの比較


97

可変コレクションでvalを使用する場合と、不変コレクションでvarを使用する場合について、Scalaにガイドラインはありますか?それとも、不変のコレクションでvalを本当に目指すべきでしょうか?

両方の種類のコレクションがあるという事実は、私に多くの選択肢を与えてくれます、そして私はしばしばその選択をする方法を知りません。


回答:


104

これはかなりよくある質問です。難しいのは重複を見つけることです。

参照の透明性に努めるべきです。私は表現「e」を持っている場合の手段がそのあることを、私が作ることができるval x = e、と交換してくださいex。これは、可変性が壊れる性質です。設計上の決定を行う必要がある場合は常に、参照の透明性を最大化してください。

実際問題として、メソッドローカルvarvarメソッドをエスケープしないため、最も安全です。メソッドが短い場合はさらに良いです。そうでない場合は、他のメソッドを抽出して削減してみてください。

一方、可変コレクションは、逃げなくても逃げる可能性があります。コードを変更するときに、他のメソッドに渡したり、コードを返したりしたい場合があります。それは、参照の透明性を壊すようなものです。

オブジェクト(フィールド)でもほぼ同じことが起こりますが、より悲惨な結果が生じます。どちらの方法でもオブジェクトは状態を持つため、参照の透明性が失われます。ただし、変更可能なコレクションを使用すると、オブジェクト自体でさえ、誰が変更しているかを制御できなくなる可能性があります。


38
ニース、私の心に新たな大きな絵:優先immutable valオーバーimmutable varの上にmutable valオーバーmutable var。特にimmutable varmutable val
Peter Schmitz、2012

2
ローカルのmutableを閉じても(リークのように、変更できる副作用の「関数」のように)可能性があることに注意してくださいvar。不変コレクションを使用するもう1つの優れた機能は、var変更が発生した場合でも、古いコピーを効率的に保持できることです。
神秘的なダン

1
tl; dr:他の関数に渡す場合、前者の場合、その関数は変更できないので、優先var x: Set[Int] val x: mutable.Set[Int]てください。xx
pathikrit

17

不変コレクションを使用していて、それらを「変更」する必要がある場合、たとえば、ループに要素を追加varする場合、結果のコレクションをどこかに格納する必要があるため、s を使用する必要があります。不変コレクションのみを読み取る場合は、vals を使用します。

一般に、参照とオブジェクトを混同しないようにしてください。valsは不変の参照(Cの定数ポインター)です。つまり、を使用するとval x = new MutableFoo()、ポイントするオブジェクトを変更できますが、xポイントするオブジェクトを変更する ことはできませんx。を使用すると、逆のことが成り立ちますvar x = new ImmutableFoo()。最初のアドバイスを取り上げますval。参照ポイントをどのオブジェクトに変更する必要がない場合は、sを使用します。


1
var immutable = something(); immutable = immutable.update(x)不変のコレクションを使用する目的を無効にします。参照の透過性はすでにあきらめており、通常は時間の複雑さが増すミュータブルコレクションから同じ効果を得ることができます。4つの可能性(valおよびvar、ミュータブルおよびイミュータブル)のうち、これは最も意味がありません。私はよく使いますval mutable
Jim Pivarski 2013

3
@JimPivarski私は同意しません。他の人もそうですが、ダニエルの回答とピーターのコメントを参照してください。データ構造を更新する必要がある場合は、可変のvalではなく不変のvarを使用すると、ローカルの仮定を破るような方法で他のユーザーによって変更されるリスクなしに、構造への参照をリークできるという利点があります。これらの「その他」の欠点は、古いデータを読み取る可能性があることです。
Malte Schwerhoff 2013

私は気が変わって同意します(元のコメントは履歴に残します)。私はこれを使用して以来、特に、var list: List[X] = Nil; list = item :: list; ...以前は別の方法で書いたことを忘れていました。
Jim Pivarski、2013

@MalteSchwerhoff:一貫性が重要な場合は、プログラムの設計方法によっては、「古いデータ」が実際に望ましい。これは、たとえば、Clojureで同時実行が機能する方法の主要な基本原則の1つです。
エリックカプルン2014年

@ErikAllik失効したデータ自体は望ましいとは言えませんが、クライアントに提供したい/必要とする保証に応じて、データが完全に正常である可能性があることに同意します。または、古いデータを読み取るという唯一の事実が実際に利点である例はありますか?古くなったデータを受け入れることによる結果を意味するのではなく、パフォーマンスが向上するか、APIが単純になる可能性があります。
Malte Schwerhoff 2014年

9

これに答える最良の方法は、例を使用することです。何らかの理由で単純に数値を収集するプロセスがあるとします。これらの番号をログに記録し、コレクションを別のプロセスに送信してこれを実行します。

もちろん、ロガーにコレクションを送信した後も、まだ番号を収集しています。また、実際のロギングを遅らせるロギングプロセスのオーバーヘッドがあるとします。うまくいけば、これがどこに向かっているのかがわかるでしょう。

このコレクションをミュータブルval(継続的に追加しているためミュータブル)に保存する場合、これは、ロギングを実行するプロセスが、コレクションプロセスによってまだ更新されている同じオブジェクトを参照することを意味します。そのコレクションはいつでも更新される可能性があるため、ログを記録するときは、実際に送信したコレクションをログに記録していない可能性があります。

immutableを使用する場合var、ロガーに不変のデータ構造を送信します。コレクションにさらに数値を追加すると、新しい不変データ構造に置き換えられます。これは、ロガーに送信されたコレクションが置き換えられるという意味ではありません。送信されたコレクションをまだ参照しています。したがって、ロガーは実際に受信したコレクションをログに記録します。var


1

同時実行シナリオではどのコンボを使用するかという問題がさらに重要になるため、このブログ投稿の例がさらに明らかになると思います:同時実行の不変性の重要性。そして、その間、同期対@volatile対AtomicReferenceのようなものの好ましい使用法に注意してください:3つのツール


-2

var immutableval mutable

この質問に対する多くの優れた回答に加えて。の潜在的な危険性を示す簡単な例を次に示しval mutableます。

可変オブジェクトは、メソッド内で変更することができ、それらをパラメーターとして受け取りますが、再割り当ては許可されません。

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

結果:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

var immutable再割り当てが許可されていないため、このようなことがで発生することはありません。

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

結果:

error: reassignment to val

関数パラメーターはvalこのように扱われるため、再割り当てはできません。


これは誤りです。このエラーが発生した理由は、2番目の例でVectorを使用したためです。これは、デフォルトでは不変です。ArrayBufferを使用すると、正常にコンパイルされ、同じ要素が新しい要素に追加され、変更されたバッファを出力するだけで同じことが行われることがわかります。pastebin.com/vfq7ytaD
EdgeCaseBerg

@EdgeCaseBerg、私は2番目の例で意図的にベクターを使用mutable valしていimmutable varます。これは、最初の例の動作がで不可能であることを示すためです。ここで何が間違っていますか?
Akavall 2017

ここでリンゴとオレンジを比較しています。ベクトルには+=配列バッファのようなメソッドはありません。あなたの答え+=x = x + yそれがそうでないのと同じであることを意味します。関数paramsがvalsとして扱われるというあなたの声明は正しいです、そしてあなたはあなたが言及したエラーを受け取りますが、あなたが使用し=たからです。ArrayBufferでも同じエラーが発生する可能性があるため、ここでのコレクションの可変性はあまり関係ありません。OPが話していることを理解していないので、それは良い答えではありません。これは、意図しない場合に可変コレクションを渡す危険の良い例です。
EdgeCaseBerg 2017

@EdgeCaseBergしかしArrayBuffer、を使用して、で得られる動作を再現することはできませんVector。OPの質問は広範囲ですが、彼らはいつ使用するかについての提案を探していたので、可変コレクションを渡すことの危険性を示しているので、私の答えは有用だと思います(役に立たvalないという事実)。immutable varより安全ですmutable val
Akavall 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.