まず
Rubyにおいてオブジェクトが イミュータブル(不変) か ミュータブル(可変) かという区分は、プログラムの設計とパフォーマンスに重大な影響を与えます、と言われているが正直なところどのように影響しているのかわからなかったのでまとめました
イミュータブルオブジェクトの特徴と利点
イミュータブルオブジェクトは、一度作成されるとその状態が変更されないオブジェクトらしい。
Rubyでは、数値やシンボル、凍結された文字列などがイミュータブルに該当
ここで数値がイミュータブルなのは違和感というかよくわからなかったので深掘りしてみよう
数値のイミュータブル性
Rubyにおける数値はイミュータブル!
これは、数値に対する操作が元の数値を変更することなく、常に新しい数値オブジェクトを生成する
具体例
a = 10
b = a + 5
# aは10のまま、bは15になる
この場合、a の値は変更されず、a + 5 の計算により新しい数値 15 が作成され b に代入される。
これだとわかりにくいというかよくわからなかったのでもう少しやってみる
a = 100
b = 100
c = 100
# a, b, cのオブジェクトIDを確認
id_a = a.object_id
id_b = b.object_id
id_c = c.object_id
puts id_a, id_b, id_c
こうした時にputsで表示されるidsは同じになる
イミュータブルのメリット
安全性と予測可能性: 数値が不変であるため、値が意図せず変更されることがなく、バグの原因を減らすことができる。
↑については自分で検証する方法がわからなかった、参考書的な知識だとこうらしい
- メモリ効率: 同じ値を持つ数値はメモリ上で共有され、新しいメモリ領域を確保する必要がなくなる。
- ガベージコレクションの効率化: オブジェクトが不変であれば、ガベージコレクタの負担が軽減され、パフォーマンスが向上します。(ガベージコレクション(メモリの不要なデータを回収し、再利用可能にするプロセス))
シンボルのイミュータブル性
シンボルもイミュータブル。一度作成されたシンボルは変更されず、同じシンボルはプログラム全体で共有される
symbol = :symbol
symbol_another = :symbol
symbol == symbol_another # => true
ミュータブルオブジェクトの特徴と取り扱い
一方、ミュータブルオブジェクトは、作成後にその状態を変更できるオブジェクト
Rubyでは配列、ハッシュ、非凍結の文字列などがミュータブル
ミュータブルオブジェクトの例
配列やハッシュはミュータブルであり、要素の追加や変更が可能
具体例
array = [1, 2, 3]
array << 4
# arrayは[1, 2, 3, 4]になる
この例では、配列 array に新しい要素を追加できる→ミュータブルだね!
ミュータブルオブジェクトの取り扱い
ミュータブルオブジェクトの柔軟性は便利、でも変更による副作用やデータの競合に注意する必要があり!
イミュータブルとミュータブルの選択
プログラムの安全性、効率性、予測可能性を考慮して、イミュータブルとミュータブルのどちらを使用するかを選択するのが大事かなと
その他重要そうな要素をまとめました
文字列の凍結
Ruby 2.3以降では、マジックコメント # frozen_string_literal: true を使用してファイル内のすべての文字列リテラルを自動的に凍結(イミュータブル化)することが可能
# frozen_string_literal: true
str1 = "Hello"
str2 = "Hello"
puts str1.object_id == str2.object_id # => true
このようにした時、同じオブジェクトIDになるのは凍結された文字数リテラルを使っているから
オブジェクトの凍結
freeze メソッドを使用して、ミュータブルオブジェクトをイミュータブルにすることができます。これにより、オブジェクトの不変性を保証することが可能
obj = [1, 2, 3]
obj.freeze
obj << 4 # => can't modify frozen Array
配列obj
が凍結されているため、新しい要素を追加しようとするとエラーが発生する
オブジェクトの複製
dup または clone メソッドを使ってオブジェクトの複製を作成することで、元のオブジェクトを変更から保護する
original_array = [1, 2, 3]
duplicate_array = original_array.dup
duplicate_array << 4
puts original_array.inspect # => [1, 2, 3]
puts duplicate_array.inspect # => [1, 2, 3, 4]
dupを使用して配列の複製を作成し、複製された配列に変更を加えても元の配列は変更されない
dupとcloneの主な違いは、cloneは凍結状態や特異メソッドも複製する点
https://www.rubyguides.com/2018/11/dup-vs-clone/
https://www.rubysos.com/std-lib/whats-the-difference-between-rubys-dup-and-clone-methods/
https://koenwoortman.com/ruby-clone-object/