これは何?
RubyGoldの勉強をする中で定数探索について理解が浅く、レキシカルスコープと継承の違いを理解できていなかったため学んだことをまとめた記事になります。
まずレキシカルスコープについて
クラスやメソッドなどの定義内にできるスコープのことで、見かけ上の静的なスコープ。"静的"というからには"動的"なスコープもあり、そちらはダイナミックスコープというものがある。(Wikipdeiaを参照するとLISPやEmacs Lispなどがダイナミックスコープにあたるらしい)
定数探索時、レキシカルスコープ内の定数を優先する
大前提として、定数探索はレキシカルスコープを優先して探索し、レキシカルスコープ内になければ継承関係を辿って探索する。
レキシカルスコープと名前空間は違う
下記の2つはどちらもクラス"M::C"を定義。
上のコードはモジュール"M"の中にネストしてクラス"C"を定義している。下のコードは名前空間としてモジュール"M"を定義したのちクラス"M::C"を定義。
まずこちらのコードはネスト定義の中、つまりレキシカルスコープ内にあたるため、クラス"M::C"から定数CONSTを参照することが可能。
module M
CONST=10
class C
def print
puts CONST
end
end
end
M::C.new.print # => 10
こちらはレキシカルスコープでもなければ継承関係にもないため、Mに定義した定数CONSTはクラス"M::C"からは参照できない。
module M
CONST=10
end
class M::C
def print
puts CONST
end
end
M::C.new.print # uninitialized constant M::C::CONST (NameError)
レキシカルスコープ内で継承関係があるかどうかを確認
C1 は C を継承しているのではなく、ネスト定義に含まれているだけ。
つまりレキシカルスコープ内に含まれていることになる。
class C
CONST=100
class C1
def self.test
puts CONST
end
end
end
puts C::C1 < C ? true : false
p C::C1.ancestors
C::C1.test
# 実行結果
false # 継承はしていないため false
[C::C1, Object, Kernel, BasicObject] # 継承ではないため C は含まれない
100
継承の場合は下記。
class C2
CONST=100
end
class C3 < C2
def self.test
puts CONST
end
end
puts C3 < C2 ? true : false
p C3.ancestors
C3.test
# 実行結果
true # C3 は C2 を継承しているため true
[C3, C2, Object, Kernel, BasicObject]
100
レキシカルスコープ内とスコープ外
下記のソースコードについて、どちらもレキシカルスコープで定数が参照できるように見えるが、ネスト定義から外れるとスコープ外となる。Module::nesting を使って確認できる。
- レキシカルスコープ内
module M
CONST = 10
class C
def hello
p Module::nesting
p CONST
end
end
end
M::C.new.hello
# 実行結果
[M::C, M] # ネスト定義の順を返す
10 # レキシカルスコープ内なので M::C にCONSTが未定義の場合 M::CONST を参照する。
- レキシカルスコープ外
module M
CONST = 10
end
class M::C
def hello
p Module::nesting
p CONST rescue p "error."
end
end
M::C.new.hello
こちらは"M"がネストに含まれていない。(2番目のサンプルとほぼ同じ)
# 実行結果
[M::C] # ネスト定義の順を返す
"error." # スコープ外のためエラーとなり、定数は見つからず例外が発生しrescueで補足される
名前探索でもレキシカルスコープが優先される
たとえば定義が重複しているように見えるクラス名で継承させた場合どうなるか。
クラス"C"が2つあるように見えるが、"B::C"と"C"は別のクラスであることを理解できていれば特に難しくない。
class C
CONST = 10
def hello
"Hello world"
end
end
module B
class C
def hello
"HelloHello"
end
end
class C2 < C
end
end
# どちらが実行される?
p B::C2.new.hello
p B::C2.ancestors
レキシカルスコープが優先されるため、B::C が継承され B::C#hello が実行される。
# 実行結果
"HelloHello" # "B::C::hello"が実行される
[B::C2, B::C, Object, Kernel, BasicObject] # "B::C2"は"B::C"を継承する
クラス"B::C"が定義されていなかった場合はクラス"C"が継承される。
class C
CONST = 10
def hello
"Hello world"
end
end
module B
#class C
# def hello
# "HelloHello"
# end
#end
class C2 < C
end
end
p B::C2.new.hello
p B::C2.ancestors
"Hello world" # "C::hello"が実行される
[B::C2, C, Object, Kernel, BasicObject] # "B::C2"は"C"を継承する