Rubyの定数は難しい

ネストしたモジュールとかクラスの定義方法には、まあ二つくらい書き方がある。

バカ正直にネストしたモジュールを並べる方法。

module A
  module B
    module C
      ...
    end
  end
end

::演算子を使う方法。

module A::B::C
  ...
end

この二つは同じだと思っていたのだけど、同じではなかったことに気付いたという話。

私は、これまでは後者の方を好んで書いていた。なぜかというと、定数のネストの途中に出てくるAA::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::XA::B::XA::X::Xが探索されて、この場合はA::B::Xに解決される
  • 一方でA::B::Cという名前のモジュール式の中に書いたXは、モジュール式をさかのぼることができないので、A::B::C::X::Xのどちらかになるのでconst_missing

うーむ。

Effective Ruby

Effective Ruby

これはRailsアプリを書いている最中に発生した問題でなぜか、なぜかモジュールのautoloadに失敗するという問題で、普通に二晩くらい悩んだ結果*1、そういえばEffective Rubyに「定数の参照はレキシカルスコープだ」って書いてあった話を思い出したりとかして問題が解決したので、これはとても良い本であると言える。*2

*1:大げさに言っている。30分悩んで、忘れて二晩寝て、思い出して20分調べて解決した。

*2:冗談。良い本だとは思うけど、この問題は多分out of scopeだろう。