次の「B」には「A」からはアクセスできないのに、メイン環境からはアクセスできるのはなぜですか?
module A; end
A.instance_eval{B=1}
B #=> 1
A::B #=> uninitialized
(強調鉱山)のドキュメントinstance_eval
から:
レシーバー (obj) のコンテキスト内で、Ruby ソース コードを含む文字列または指定されたブロックを評価します。コンテキストを設定するために、コードの実行中に変数 self が obj に設定され、コードがobj のインスタンス変数にアクセスできるようになります。
ここではこれ以上何もしません。特に、定数の代入は、ブロックの外側のコンテキストで実行されます。観察:
irb(main):001:0> module A
irb(main):002:1> module B; end
irb(main):003:1> B.instance_eval { C = 1 }
irb(main):004:1> end
=> 1
irb(main):006:0> A::C
=> 1
. . . クラスまたはモジュール内で定義されていない定数には、グローバル スコープが与えられます。
定数の定義で重要なのは、現在のレシーバーや の値ではなく、囲んでいるレキシカル スコープですself
。
これを行う慣用的な方法は次のようになります
A.const_set(:B, 1)
A::B #=> 1
Ruby 1.8 と 1.9.2+ (1.9.1 では異なります) では、なぜ機能しないのかというと、定数ルックアップはレキシカル スコープです。説明付きの良いブログ投稿を見つけました。引用するには:
これらの規則は、定数の定義とルックアップに適用されることに注意してください。1.8 および 1.9.2 では、class_evaluated ブロックで定義された定数は、レシーバーのスコープではなく、囲んでいるレキシカル スコープで定義されます。
についても同様ですinstance_eval
。
module A; end
A.class_eval{B=1}
B # Undefined
A::B # 1
なぜそれが機能するのかについては、よくわかりません。Small Eigen Collider のような非常にメタなフレームワークを作成するときは、このようなメタプログラミングをときどき使用しますが、日常業務では使用しません。