パッケージ理解のコツ
データ型(mode)としての環境と、役割・用途としてのスコープオブジェクト、そしてさらに特定した使い方としての名前空間オブジェクトを区別する。
- 名前空間は特殊なスコープオブジェクトである。
- スコープオブジェクトは環境型のオブジェクトの使い途を意味する。
- どのような環境型オブジェクトもスコープオブジェクトになり得る。なぜなら使い方だから。
- 環境オブジェクトを名前空間にするのは手間だし、通常はやらない。
インタプリタ・トップレベルをある関数のように考える。すると、.GlobalEnvはトップレベル関数の関数フレームと解釈できる。呼び出しチェーンにおけるルートが.GlobalEnvだから、親フレームは「ない」としてもよいが、呼び出しチェーンは便宜上ループさせていると考えることができる。
トップレベルの関数フレーム=グローバル環境のスタティックスコープチェーンを決定するのがsearch()パス。パスと呼んでいるが、これはスコープチェーン。スコープチェーンの末端(ほんとの最後であるemptyenv()の手前)は常にbaseパッケージのエクスポーターと決まっている。
パッケージのスコープチェーンもトップレベルのスコープチェーンと同じだが:
- 関数フレームの親スコープとして、名前空間がある。トップレベルには固有の名前空間がない。ワークスペース=グローバル環境=関数フレームはあるが、ワークスペースの親は固有の名前空間ではない。
- パッケージのスコープチェーンは、importsによって構成される。importsはパッケージ生成時にコンパイルされた変更不可でコンパクトなデータ型式を持つが、トップレベルのimportsは生のチェーンでいつでも変更できる。
- パッケージのスコープチェーンは、いったんbaseで終わるが、その後にグローバル環境=ワークスペースに繋いでいる。
- パッケージが実行時のグローバル環境を使うことは考えにくい。したがって、パッケージの終端をemptyenv()にしても動くはず。
- その意味では、パッケージのスコープチェーンをグローバルスコープチェーンの兄弟とする方法もあったはず。そうはなってないが。
コールチェーンは、関数フレームのチェーン(スタック相当)だが、動的に構成される。親は呼び出し元関数の関数フレーム。辿って行くと、必ずグローバル環境=トップレベル関数の関数フレームに辿り着く。マルチスレッドでなければ、コールチェーンはシステム(インタプリタ)内に1本しかない。
静的スコープチェーンと動的コールチェーンはまったくの別物で、スコープチェーンがコールチェーンの影響を受けることはない。どこでどのように呼びだされても、関数に入ってからの世界の見え方は変わりない。見え方は変わらなくても景色は変わる。