web-dev-qa-db-ja.com

Scala不変のマップ、いつ可変にするか?

私の現在のユースケースは非常に簡単で、変更可能または不変のMapでうまくいきます。

不変のマップを取得するメソッドを用意します。このメソッドは、不変のマップも取得するサードパーティのAPIメソッドを呼び出します。

_def doFoo(foo: String = "default", params: Map[String, Any] = Map()) {

  val newMap = 
    if(someCondition) params + ("foo" -> foo) else params

  api.doSomething(newMap)
}
_

問題のマップは一般に非常に小さく、せいぜいケースクラスインスタンスの埋め込みリストがあり、最大で数千のエントリがあります。したがって、この場合も、不変になることによる影響はほとんどないと想定します(つまり、newMap valコピーを介してマップのインスタンスが本質的に2つある)。

それでも、いくつかのk-> vエントリが追加された新しいマップを取得するためだけに、マップをコピーして、少し困惑します。

タックしたいエントリに対して可変およびparams.put("bar", bar)などを実行し、次に_params.toMap_を使用してapi呼び出しに対して不変に変換することができます。これはオプションです。しかし、その後、可変マップをインポートして渡す必要があります。これは、Scalaのデフォルトの不変マップを使用する場合に比べて少し面倒です。

では、不変のマップよりも可変のマップを使用することが正当化/グッドプラクティスである場合の一般的なガイドラインは何ですか?

ありがとう

[〜#〜] edit [〜#〜]したがって、不変マップでの追加操作にはほぼ一定の時間がかかるようで、@ dhgと@Nicolasが完全なコピーが作成されていないという主張を確認します。これは、提示された具体的なケースの問題を解決します。

24
virtualeyes

不変のマップの実装によっては、いくつかのエントリを追加しても、実際には元のマップ全体がコピーされない場合があります。これは、不変のデータ構造アプローチの利点の1つです。Scalaは、コピーをできるだけ少なくしようとします。

この種の動作は、Listで最も簡単に確認できます。 val a = List(1,2,3)がある場合、そのリストはメモリに保存されます。ただし、val b = 0 :: aのような追加の要素を追加すると、do新しい4要素のListが返されますが、Scala not元のリストaをコピーします。代わりに、bという名前の新しいリンクを1つ作成し、既存のリストa

他の種類のコレクションについても、このような戦略を思い描くことができます。たとえば、1つの要素をMapに追加すると、コレクションは既存のマップをラップし、必要に応じてフォールバックし、APIを単一のMapであるかのように提供できます。 。

40
dhg

可変オブジェクトの使用自体は悪くありませんが、関数を純粋にし、オブジェクトを不変に保つことで副作用を回避しようとする関数型プログラミング環境では悪くなります。

ただし、関数内で可変オブジェクトを作成してこのオブジェクトを変更した場合、関数外でこのオブジェクトへの参照を解放しなければ、関数は純粋なままです。次のようなコードを使用することは許容されます。

def buildVector( x: Double, y: Double, z: Double ): Vector[Double] = {
    val ary = Array.ofDim[Double]( 3 )
    ary( 0 ) = x
    ary( 1 ) = y
    ary( 2 ) = z
    ary.toVector
}

さて、このアプローチは次の2つの場合に役立つ/推奨されると思います。(1)不変オブジェクトの作成と変更がアプリケーション全体のボトルネックである場合のパフォーマンス。 (2)コードの可読性。複雑なオブジェクトを所定の位置で変更する方が(レンズやジッパーなどに頼るよりも)簡単な場合があるためです。

14
paradigmatic

Dhgの答えに加えて、 scalaコレクション のパフォーマンス)を確認できます。追加/削除操作に線形時間がかからない場合は、単に構造全体をコピーする以外のことをする必要があります(逆は真ではないことに注意してください:構造全体をコピーするのに線形時間がかかるためではありません)

5
Nicolas

可変または不変のマップではなく、宣言されたパラメータータイプ(入力値または戻り値)としてcollections.mapsを使用するのが好きです。コレクションマップは、両方のタイプの実装で機能する不変のインターフェイスです。マップを使用するコンシューマーメソッドは、実際には、マップの実装やその構築方法について知る必要はありません。 (とにかく、それは実際にはそのビジネスのどれでもありません)。

マップの特定の構造(可変または不変)をそれを使用するコンシューマーから隠すというアプローチを採用した場合でも、本質的に不変のマップをダウンストリームで取得できます。また、collection.Mapを不変のインターフェイスとして使用することで、immutable.Map型のオブジェクトを使用するように記述されたコンシューマーで発生する「.toMap」の非効率性をすべて完全に取り除くことができます。最初のマップでサポートされていないインターフェイスに準拠するために、完全に構築されたマップを別のマップに変換する必要があることは、考えてみるとまったく不要なオーバーヘッドです。

これから数年以内に、3つの別々のインターフェイスセット(可変マップ、不変マップ、コレクションマップ)を振り返ると、99%の場合、実際に必要なのは2つ(可変マップとコレクション)だけであることがわかります。 (残念ながら)デフォルトの不変マップインターフェイスを使用すると、「スケーラブル言語」に多くの不要なオーバーヘッドが実際に追加されます。

0
Andrew Norman