Rubyの定数探索について理解したい
挨拶
ポートでバックエンドを担当している新卒の @yuki.hara です。
今回は Ruby の定数探索がどのように行われているのか気になったため、調査しました。
基本的な定数探索の仕組み
まず、Ruby の定数探索には下記の 2 つのアルゴリズムが存在します。
- 継承チェーンを利用した探索
- レキシカルスコープチェーンを利用した探索
具体的にどのように探索されているか今から見ていこうと思います。
継承チェーンを利用した探索
継承チェーンとは、クラスの継承関係のことであり、メソッドの探索の時も同様に継承チェーンは利用されています。
ちなみに継承チェーン上に存在するクラスは、ancestors
メソッドで参照することができます。
定数探索時の動きとしては下記になります。
- クラス内に参照しにいく
- 参照するものが見つからない場合は、、親クラスに参照しにいく。
- 参照するものが見つかるまで、上記 1,2 を繰り返して、継承チェーンを辿っていく。
コードの具体例
class ParentClass
CONSTANT = 'constant in ParentClass'
end
class ChildClass < ParentClass
p CONSTANT
end
# 下記が出力結果
# "constant in ParentClass"
継承チェーンは下記のようになっています。
p ChildClass.ancestors #=> [ChildClass, ParentClass, Object, Kernel, BasicObject]
レキシカルスコープチェーンを利用した探索
レキシカルスコープは静的スコープとも呼ばれており、クラスや関数の定義時にスコープが決まります。
レキシカルスコープチェーンを理解するには下記の 2 つのをまず押さえる必要がある。
nd_next
nd_class
下記が図に表したものになっています。
https://patshaughnessy.net/2013/6/4/coming-this-fall-ruby-under-a-microscope-updated-and-in-print
nd_next とは?
nd_next
は、親のレキシカルスコープを保持しているポインタです。通常のクラスを作成した場合は、トップレベルのスコープ(Object クラスのレキシカルスコープ)が保持されます。
nd_class とは?
nd_class
は、スコープに対応するクラスやモジュールの情報を保持しているポインタです。
定数探索時の動きとしては下記になります。
-
nd_class
を利用してスコープに対応するクラス or モジュールを参照する。 - クラス or モジュールが保持する定数テーブルに参照したい定数があるか確認する。
- 参照するものが見つからない場合は、
nd_next
ポインタを利用して親のレキシカルスコープに参照しにいく。 - 参照するものが見つかるまで、上記 1,2,3 を繰り返して、レキシカルスコープチェーンを辿っていく。
親のレキシカルスコープはnd_next
に保持しているが、self のレキシカルスコープは Ruby 内部のcref
にセットされています。
定数の自動読み込みと再読み込み (Classic) - Rails ガイド
コードの具体例
module NameSpace
CONSTANT = 'constant in NameSpace'
class ParentClass
p CONSTANT
end
end
# 下記が出力結果
# "constant in NameSpace"
レキシルスコープチェーンと継承チェーンはどちらが優先される?
上記で2通りの探索方法を説明してきたが、下記のような場合はどうなるでしょうか?
class ParentClass
CONSTANT = 'constant in ParentClass'
end
module NameSpace
CONSTANT = 'constant in NameSpace'
class ChildClass < ParentClass
p CONSTANT
end
end
# 出力結果
# "constant in NameSpace"
出力結果から、レキシカルスコープの方が優先されると言うのがわかる。
ruby の基本的な定数探索のアルゴリズムは説明したのでもう少し深ぼっていく。
ruby には定数の参照方法が 3 つのパターンがある。
- 絶対参照(::MainClass)
- 相対参照(MainClass)
- 修飾つき参照(MainClass::CONSTANT)
絶対参照とは?
絶対参照(::MainClass
)についてはリファレンスに下記のように記載されていました。
Object クラスで定義されている定数(トップレベルの定数と言う)を確実に参照するためには左辺無しの`::'演算子が使えます。
従ってトップレベルの定数が参照できます。
相対参照とは?
相対参照は前半部分で説明した定数探索が行われます。
修飾つき参照
定数を参照するときは基本的にこの方法が使用されます。
修飾つき参照は、下記の流れで処理されます。
- ::の左側にある定数が
cref
にセットされる。 - 継承チェーンのみを辿る。ただし、トップレベルとその祖先は探索しない。
トップレベルとその祖先は探索しない理由
https://bugs.ruby-lang.org/issues/11547
トップレベルとその祖先のクラスは探索しない理由は上記に記載してありました。
class Auth; end
class Twitter; end
上記のようなコードがあり、Twitter::Auth
で定数を参照しにいく場合にトップレベルとその祖先のクラスを探索できるとを探索できると次のような問題が起こる。
- 定数
Twitter
を相対参照する。 -
::Auth
を修飾つき参照すると、継承チェーンをたどり、Object
クラス配下にAuth
クラスが発見されるので、本来Twitter
クラスの中に定数はないので何も出力されないはずなのに出力されてしまう。
上記の理由から Ruby2.5 からトップレベルとその祖先のクラスは探索できないようになっている。
今回参照させていただいた記事・書籍
まとめ
最後まで見ていただきありがとうございます:bow:
今度はこれを踏まえて Rails が Model や Controller をどのように読み込んでいるか調査してみようと思います!
Discussion