0
1

【Ruby】レキシカルスコープと継承の定数探索の違い

Last updated at Posted at 2024-08-12

これは何?

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"を継承する
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1