Rubyの定数、注意点についてまとめておく
注意点
Rubyの定数は、多言語とは異なる仕様になっている
異なる仕様について
プログラミング言語における定数とは
書き換え不可能
wikipediaで調べてみる
一度初期化するとその内容を変更することはできない。よって、内容が変化しないことが保証される名前が必要なときに使用される。
定数の内容は変化しない、これはruby以外の言語を学んだ人であれば当たり前のことだろう。
Rubyおける定数とは
書き換え可能
Rubyの定数について詳しくみていく
定義
- アルファベット大文字で始まる識別子
例:Abc、ABC、ABc
Ruby独自の挙動について
- 一度定義した定数に再び代入を行おうとすると"警告が発生"する
警告は出るけど値は変わる
TOWN = "TOKYO"
# 破壊的変更
TOWN.downcase!
# できてしまう
TOWN #=> "tokyo"
# 再代入
TOWN = "NEWYORK"
# 警告発生するが値は変更されてしまう
warning: already initialized constant TOWN
warning: previous definition of TOWN was here
TOWN #=> "NEWYORK"
定数の変更を防ぐためには
- 検証その1
freezeメソッドを使用してオブジェクトを凍結する- 破壊的メソッド:不可能
- 再代入:可能(防げない!!)
TOWN = "TOKYO"
TOWN.freeze
# 破壊的変更
TOWN.downcase!
# 変更できない(エラーになる)
RuntimeError: can't modify frozen String
from (irb):3:in `downcase!'
# 再代入を行う
TOWN = "NEWYORK"
# 警告発生するが値は変更されてしまう
warning: already initialized constant TOWN
warning: previous definition of TOWN was here
TOWN #=> "NEWYORK"
この方法ではまだだめだ・・・
- 検証その2
定数をmodule内で定義し、moduleをfreezeする- 破壊的メソッド:可能(防げない!!)
- 再代入: 不可能
module TEST
TOWN = "TOKYO"
end
TEST.freeze
# 破壊的変更はこのままだとできる module.freezeはmoduleの参照を凍結している
TEST::TOWN.downcase! #=> "tokyo"
# 再代入は阻止できる
TEST::TOWN = "NEWYORK"
RuntimeError: can't modify frozen Module
この方法でもまだだめだ・・・
- 検証3 これでOK
定数をmoduleに格納し、module及び定数をfreezeする- 破壊的メソッド:不可能
- 再代入: 不可能
破壊的変更も再代入も防げる!!
#定数をmodule内で定義
module TEST
TOWN = "TOKYO".freeze
end
#moduleをfreeze
TEST.freeze
#破壊的変更を阻止(エラーになる)
TEST::TOWN.downcase!
RuntimeError: cant modify frozen String
# 代入を阻止(エラーになる)
TEST::TOWN = "NEWYORK"
RuntimeError: can't modify frozen Module
ただし配列の場合は以下の通り不十分!!
TEST = ["TOKYO","NEWYORK"]
TEST.freeze
# 配列に対して破壊的変更はできない
TEST.sort!
RuntimeError: can't modify frozen Array
# 配列の要素に対しては変更できる
TEST[0].downcase! #=> "tokyo"
# 配列に対しては凍結しているだけで要素は凍結されていない。
TEST.frozen? #=> true
TEST[0].frozen? #=> false
- 配列の場合は要素それぞれに対して、freezeする
TEST = ["TOKYO","NEWYORK"]
TEST.map!(&:freeze).freeze
TEST[0].downcase!
RuntimeError: can't modify frozen String
- 定数の値が配列の時は以下で対応
module TEST
TOWN = ["TOKYO","NEWYORK"].map!(&:freeze).freeze
end
TEST.freeze
# TOWNへの再代入
TEST::TOWN = "HAWAI"
RuntimeError: can't modify frozen Module
# TOWNの破壊的変更
TEST::TOWN.sort!
RuntimeError: can't modify frozen Array
# TOWNの要素の破壊的変更
TEST::TOWN[0].downcase!
RuntimeError: can't modify frozen String
# TOWNの要素への代入
TEST::TOWN[0] = "HAWAI"
RuntimeError: can't modify frozen Array
まとめ
Rubyの定数は、書き換えられるようになっている。
定数は、同じオブジェクト(同じオブジェクトID)を指すことを意味する。
ただし、定数を書き換える、書き換えないはあくまで紳士協定に基づく。
moduleそのものをfreezeするとその中の定数の参照先をfreezeできる。
全てfreezeをつけると定数を変更不可にできるが、Rubyの思想としては、全ての箇所にfreezeをつけるのは固い発想で「freezeを全部つけなくても、紳士協定でやろう」というのがRubyっぽい感じ。