Rubyの定数は難しい
ネストしたモジュールとかクラスの定義方法には、まあ二つくらい書き方がある。
バカ正直にネストしたモジュールを並べる方法。
module A module B module C ... end end end
::
演算子を使う方法。
module A::B::C ... end
この二つは同じだと思っていたのだけど、同じではなかったことに気付いたという話。
私は、これまでは後者の方を好んで書いていた。なぜかというと、定数のネストの途中に出てくるA
やA::B
がモジュールなのかクラスなのかを考えなくて良いからだ。純然たる名前空間として使うときはモジュールが使われるが、クラスのこともある。考えたくない。
この二つの違いは、定数を参照するときである。具体的に言うと、次のような定数A::B::X
を参照するときに違いが生まれる。
module A module B X = "A::B::X" end end module A module B module C p X # => ちゃんと動く end end end module A::B::C p X # => const_missing end
これはかなり難しい……
- 定数はネストした構造を持つことがある(これは実行時に値から参照できる構造)
- 一方でプログラム中の定数の参照は、構文的な構造に依存するもので、実行時の構造は関係がない(Lexical scopeというやつであってる?)
A
の中のB
の中のC
の中に書いたX
は、それぞれのモジュール式をさかのぼっていって、A::B::C::X
かA::B::X
かA::X
か::X
が探索されて、この場合はA::B::X
に解決される- 一方で
A::B::C
という名前のモジュール式の中に書いたX
は、モジュール式をさかのぼることができないので、A::B::C::X
か::X
のどちらかになるのでconst_missing
うーむ。
- 作者: Peter J.Jones
- 出版社/メーカー: 翔泳社
- 発売日: 2015/01/19
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
これはRailsアプリを書いている最中に発生した問題でなぜか、なぜかモジュールのautoloadに失敗するという問題で、普通に二晩くらい悩んだ結果*1、そういえばEffective Rubyに「定数の参照はレキシカルスコープだ」って書いてあった話を思い出したりとかして問題が解決したので、これはとても良い本であると言える。*2