Immutableなクラス

Rubyリテラルについて考える。


ソースに直で文字列や数字を書いたときに何が起こるのか。
コンパイルしたときにオブジェクトが生成される?実行時に生成される?
固定値なのに実行時に生成されたら無駄な負荷がありそうな気はするが、自己破壊メソッドで変更されたら固定値が変わってしまうから、それはそれで困ることになる。
ソースを読もうと思ってもどこを読めばいいのかわからないから、実験してみよう。
まずはStringから。

a = "aaa"
for i in 0..1
  b = a.capitalize!
end

p b # => nil

for i in 0..1
  c = "aaa".capitalize!
end

p c # => "Aaa"

String#capitalize!は文字列の先頭を大文字に変更するメソッドで、変更が無ければnilが返る。
aに"aaa"を代入した場合、aの指してるオブジェクトが変更されるから、2回実行すると答えはnilになる。
リテラルの"aaa"に対してcapitalize!すると、2回実行しても"Aaa"が返ってくる。
これはつまり、文字列についてはコンパイル時に生成されて参照時にdup、もしくは、参照時にnewされていると考えられる。


では次に、このあいだ作ったadd!を使ってFloat。

require 'floattest'

for i in 0..1
  a = 1.0.add!(1.0)
end

p a # => 3.0

この結果は、リテラルの固定値が変更されていることを意味する。
コンパイル時にnewされて保持されている1.0に対して、自己破壊加算した結果、見た目1.0のオブジェクトが2.0に、そして3.0になってしまったのだ。


RubyのNumericクラス群はImmutableValue(即値)であるように設計されている。
Fixnumは今でもそうだ。Floatオブジェクトは即値ではないが、いつ実装を変更して即値にしてもOKなことが保証されるべきで、つまりNumeric関連クラスに自己破壊メソッドを導入するのは設計思想的に間違っている。
逆に言えば、今後Floatクラスが即値は無理だとしても、即値とオブジェクトの間の速度を重視した実装になる可能性が残されていると言える。