LL脳がscalaの勉強を始めたよ その60

Scalaコップ本の17章の続きをやりますよー、昨晩は酔っ払いで時間が取れなかったので今回はちょこっとだけやります

ミュータブルとイミュータブル

ミュータブル版とイミュータブル版のコレクションのどちらを使うべきか、という悩みに対してのコップ本的オススメはイミュータブル版でまず実装して、必要になったらミュータブル版に切り替えるのがいいYO(`・ω・´)、ということらしいです。これはイミュータブル版のほうが要素の変化がないためコレクションの動作を想定しやすいからみたいです。


また、上記理由よりミュータブル版コレクションを使ったコードが煩雑になって正しい実装になっているかどうか不安定になってきたらイミュータブル版に切り替えてみるのもありとのことです。


とりあえず、「まずはイミュータブル版コレクション」を標語にするのがよさそうですネ

イミュータブルコレクションの利点

イミュータブル版コレクションを使う利点としては次のものがあるそうです

  • 要素の変化がないのでロジックの推論がしやすい
  • ミュータブル版よりもデータをコンパクトに格納できる


後者の例(必要データ領域)をあげるとすると次のような感じになるみたいです

  • 空のミュータブルマップ(HashMapのデフォルト実装)
    • 約80バイトのスペースを消費
    • エントリーを1つ追加するごとに約16バイトずつ増えていく
      • 要素4つの場合は144バイト?
  • 空のイミュータブルマップ
    • ポインターフィールド1個分(すべての参照で共有される単一オブジェクト)
    • 要素が4個までのイミュータブル版は単一オブジェクトに格納
      • 格納されたオブジェクトの大きさは16 〜 40バイト
      • 単一オブジェクトはMap1 ~ Map4とSet1 ~ Set4のインスタンス


上記理由から要素数の少ないマップやセットはイミュータブル版のほうがデータ使用が小さいので、小さなコレクションがたくさんある場合はイミュータブル版に切り替えればデータ領域を節約してパフォーマンスを向上できる可能性があるらしいです。

シンタックスシュガー

イミュータブルなマップや集合は+=メソッドをサポートしていないけども、擬似的に使うことができますよー、って話です。イミュータブル版での+=は格納する変数をvar宣言した場合のみa += bをa = a + bと解釈するみたいです。

まずはval宣言でメソッドの存在を確認してみますよ

// とりあえず集合を定義してみますよー
scala> val people = Set("Taro", "Jiro")
people: scala.collection.immutable.Set[java.lang.String] = Set(Taro, Jiro)

// += 演算は怒られますねー
scala> people += "Saburo"
<console>:6: error: reassignment to val
       people += "Saburo"
              ^

それではvar宣言で同じものを試してみますよ

// var宣言で集合定義です
scala> var people = Set("Taro", "Jiro")
people: scala.collection.immutable.Set[java.lang.String] = Set(Taro, Jiro)

// +=で要素を追加しますよ
// 実際は処理結果が新しいpeopleに再代入されます
scala> people += "Saburo"
// 無事追加できました
scala> people
res2: scala.collection.immutable.Set[java.lang.String] = Set(Taro, Jiro, Saburo)

ちなみに、上記のような置き換えは=で終わる演算全てに適用されるとか…
せっかくなので他の演算子を試してみますよー

// 集合宣言です
scala> var people = Set("Taro", "Jiro")
people: scala.collection.immutable.Set[java.lang.String] = Set(Taro, Jiro)

// 要素を取り除いてみます 
scala> people -= "Jiro"
// 結果です
scala> people
res4: scala.collection.immutable.Set[java.lang.String] = Set(Taro)

// 要素を複数追加してみます
scala> people ++= List("Siro", "Goro", "Rock")
// 結果ですよー
scala> people
res6: scala.collection.immutable.Set[java.lang.String] = Set(Taro, Siro, Goro, Rock)

上記のような操作は当然マップでもできるので、それもやってみますかねー

// マップを定義しますよ
scala> var greeting = Map("Hello" -> "World", "Good" -> "Morning")
greeting: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(Hello -> World, Good -> Morning)

// 要素を追加してみますねー
scala> greeting += ("Hi" -> "Bye")
// 結果です
scala> greeting
res25: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(Hello -> World, Good -> Morning, Hi -> Bye)

// 要素を取り除いてみますかねー
scala> greeting -= "Good"
// 結果でしたー
scala> greeting
res27: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(Hello -> World, Hi -> Bye)

ちなみに+=等のコード自体はミュータブル版でもそのまま利用できるので、イミュータブル版の呼び出しさえしてしまえば、(ほとんど)そのまま実行することができますネ。まあ全てではないのですが、少なくともコードの書き換えは減るYO!ってことみたいです。

// ミュータブル版の呼出で以下のMapは全てミュータブルマップとして実行します
scala> import scala.collection.mutable.Map
import scala.collection.mutable.Map

// Map定義デス
scala> var greeting = Map("Hello" -> "World", "Good" -> "Morning")
greeting: scala.collection.mutable.Map[java.lang.String,java.lang.String] = Map(Good -> Morning, Hello -> World)

// +=は実行できました
scala> greeting += ("Hi" -> "Bye")
scala> greeting
res29: scala.collection.mutable.Map[java.lang.String,java.lang.String] = Map(Hi -> Bye, Good -> Morning, Hello -> World)

// -=も実行できましたねー
scala> greeting -= "Good"
scala> greeting
res31: scala.collection.mutable.Map[java.lang.String,java.lang.String] = Map(Hi -> Bye, Hello -> World)

ちなみにこのような=で終わる演算子を再代入に置き換えるシンタックスシュガーってやつはコレクション以外の値でも使えるみたいです。とりあえず浮動小数点で試してみたサンプルをのっけときますねー

scala> var double = 3.0
double: Double = 3.0

// 加えます
scala> double += 0.1

// 引きます
scala> double -= 0.04

結果です
scala> double
res34: Double = 3.06

うん、よく考えたら他の言語でも使う演算だな(`・ω・´)でも再代入だからval変数では使えないよ、ってc⌒っ゚д゚)っφ メモメモ...

いじょー

時間切れなのでここまでですよー、次回はコレクションの初期化からやります(`・ω・´)