ハツカネズミの恋

Lisp のもろもろ,おぼえがき

frozen-string-literal: true についてのメモ

脳死で Rubocop を導入すると,あらゆる .rb ファイルの冒頭に自動で挿入される意味不明な文字列として名高い #frozen-string-literal: true .意味不明な文字列は有意味な文字列に変換してしまえば基本的に問題ないはずなので,調べた.

tl; dr

文字通り,文字列型のリテラルを凍結 = イミュータブルにする(?)ためのおまじないだった.とはいえ,破壊的メソッドの適用を防ぐのが関の山で,再代入は防げない.弱い.

frozen-string-literal

freeze メソッドで加工された文字列リテラルのこと:

"hoge".freeze 

ではなぜ freeze なるメソッドが必要なのか.

ref.xaio.jp

freezeメソッドは、オブジェクトを凍結、つまり変更不可にします。

freeze は Object クラスに定義されている.Object クラスはすべてのクラスの親クラスなので,Ruby ではすべてのオブジェクトに freeze が使える.裏を返すと,Ruby ではすべてのオブジェクトがデフォルトでは変更可能なのか……?

そう思い立って,変更不可能な値の代表である定数について少し調べた.

Ruby における定数

定数の一般的な定義は以下:

プログラミングにおいて定数(「ていすう」または「じょうすう」、英: constant)とは、変数同様プログラムのソースコードにおいて、扱われるデータを一定期間記憶し必要なときに利用できるようにするために、データに固有の名前を与えたものである。 ただし変数とは異なり、一度初期化するとその内容(値)を変更することはできない。よって、内容が変化しないことが保証される名前が必要なときに使用される。

なるほど.

一方,Ruby の定数:

アルファベット大文字 ([A-Z]) で始まる識別子は定数です。 他にも、ソースエンコーディングUnicode の時は Unicode の大文字またはタイトルケース文字から始まる識別子も定数です。 Unicode 以外の時は小文字に変換できる文字から始まる識別子が定数です。 定数の定義 (と初期化) は代入によって行われますが、メソッドの中では定義できません。一度定義された定数に再び代入を行おうとすると警告メッセージが出ます。定義されていない定数にアクセス すると例外 NameError が発生します。

要するに,変更しようとすると怒られは発生するけど,結局値は変わる.つまりミュータブル.ミュータブルな定数という形容矛盾.羊頭狗肉

CITY = "SHIBUYA"

# 破壊的メソッドのレシーバにすると……
CITY.downcase!
CITY #=> "shibuya"
# 変わる

# 再代入
CITY = "SHINJUKU"
warning: already initialized constant CITY
warning: previous definition of CITY was here
# 怒られる
CITY #=> "SHINJUKU"
# しかし変わる

frozen-string-literal を使ってみる.

#frozen-string-literal: true

CITY.downcase!
CITY #=> RuntimeError: can't modify frozen String
    from (irb):3:in 'downcase!'
# 破壊的操作はランタイムエラーとして防げる

# 一方再代入は……
CITY = "SHINJUKU"
warning: already initialized constant CITY
warning: previous definition of CITY was here
# 怒られる

CITY #=> "SHINJUKU"
# しかし変わる

まとめ

Ruby ではクラスもモジュールも定数らしい.生成されたクラスオブジェクトが指定したクラス名の定数に代入されていると考えれば,定数がミュータブルなのも仕方ないのかも.もっと掘ったらもっと面白いことが分かりそう.近々掘る.