読者です 読者をやめる 読者になる 読者になる

「オブジェクトをイミュータブルにしろ」って言うけど、それってたとえば状態が変わったらオブジェクト作り直すってことでしょ、ちょう非効率じゃん。って思ってたんだけど、

programming

オブジェクトの内部の値がイミュータブルであれば、今後もその値は変更されないことが保証されているので、新しい状態を持った新しいオブジェクトの内部の値のうち、変更のない部分(つまり値のうちのほとんど)は古いオブジェクトの値をそのまま参照すればよく、コピーする必要がないということを @takkkun が言っていて(正確には、イミュータブルなリストに新しい値を追加した新しいリストを作るときには、中身をコピーする必要ない。変更されないことが保証されてるから、という話だった)目から鱗が落ちたのでここに記して置こうとおもった。

で終わろうと思ったんだけど、もう少しちゃんと書く。

ミュータブルな世界では同一性の問題がある。

たとえば playerA と playerB の HP がたまたまおなじ 10 であったとしても playerA と playerB の HP 変数が同じ数値オブジェクトを参照していたらまずい。これは当然で、同じ数値オブジェクトを参照していたら playerA の HP が 3 減ったときに playerB の HP も同じく減ってしまう。playerA と playerB は同一のオブジェクトではないのに、同じものを参照していたらまずい、ということだ。

こういう問題があるので、「A と B 、別のオブジェクトはそれぞれの内部状態を別のメモリアドレスに保持しなければならない」というのがミュータブルな世界における常識、基本だと思う。そうしないとどこで変わっちゃうかわかんないからね。

でもイミュータブルなら心配ないよ!

そういうミュータブルな世界に慣れきってしまっていたわたしは「イミュータブルってことは状態が変わったらオブジェクト作り直すとか、毎回コピーが発生してちょう馬鹿じゃん」って思ってたんだけど、冒頭に書いたようにそうではない。

すべてがイミュータブルな世界では、playerA の HPが減ったら playerA を 作り直さないといけなくなるんだけど、ここで「新 playerA」と「旧 playerA」が生まれる。

「旧 playerA」と「新 playerA」が別々のオブジェクトなので、ミュータブルな世界の常識だと、旧の内部状態からHPだけを書き換えてまるっとコピーして「新playerA」を作らないとね、って話になるんだけど、イミュータブルな世界では、あるメモリアドレスに置いてある値は決してそのあと変わらないことが保証されているので、新 playerA を作るためにわざわざ「別のメモリアドレスに旧の値を全部コピー」なんてしなくていい。新 playerA の内部状態のうちほとんどは旧 playerA と同じメモリアドレスを参照するようにして、HP だけは別のメモリアドレスに作った新しい値を参照するようにすればよい。

こうすれば、例えばいくら大きな HashMap であろうといくら大きな List であろうと、「値を追加した新しい Map や List を作りたい」ってときに「全部コピーして作り直す」必要がなくなる。

これって、わかってしまいえば当たり前の話なんだけど、ミュータブルな世界の常識にとらわれているわたしが持っている「イミュータブルって言ってもさー、これどうすんのよ疑問」の、大きなもののうちのひとつが氷解して「エウレーカ!」ってなったので書いた。