モジュールシステムの意味論と実装について考える
必要要件
理想としては、以下を全部満たしたい
- 分割コンパイルしたうえで、コンパイル済みのモジュールは変更がなければ再利用できる
- 操作的意味論的にもある程度一貫性がある
- モジュール内ではシンボルの相互参照が可能(後から定義されるシンボルでも参照可能)
で、デザインチョイスのトレードオフとしては意味論を楽にしようと思うと分割コンパイルがめんどくさくなる
複数のコードを合体させるときに、流用されるコードの複製が増える問題
ただ、どっちみちDSPにおけるコードの量なんてたかが知れているのでは?という問題も
実際、そこまでモジュール内での相互参照って必要か?というのもある、Letrecで十分じゃね?
モジュールの循環参照が起きたらどうなるのかという問題もある
モジュール内での宣言一覧
//関数のvisibilityセッティングはRustとおなじ感じ
pub fn (){
}
pub const Foo = 100
pub type Bar = Constructor(()->float) //新しい型宣言
pub type alias Hoge = ()->float //エイリアス宣言
モジュールの宣言
mod(macro) modname{
//toplevel_decls
}
- モジュール単位でMIRとバイトコードを生成して、あとからリンクできるようにする
- ただし、ステージ0マクロの展開もしないといけないので、ASTも出力して保持していないといけない
モジュールを値としてレコード型に型付けできると話が早いんだけど、それ一級モジュールの機能だよな
意味論
stage(1)
mod(0) modname{
//toplevel_decls
fn foo(){
...
}
}
modname::foo!()
これがあったとすると、
extern modname = ${
foo = | | { },
...
}
modname.foo!()
こういう感じかなー、あ、でもこうするとモジュール内での相互参照が解決できないか
トップレベルでのモジュールの名前解決は後から定義されたシンボルも参照できるので、型推論のやり方はなんか考える必要がありそう
一旦トップレベルの宣言の名前だけを回収して、あとから実際の定義の型推論を行っていく形になる
しかし、そうするとラムダ計算として定義する旨味はあんまりないのでは、という気がしてくる
extern name : Type
という宣言に変換さえできれば意味論を保ってコンパイルはできそう
型がつけられさえすればいいから、そこで循環する定義にならなければOK
//modA.mmm
use modB
pub fn hoge(){
modB::hoge()
}
//modB.mmm
use modA
pub fn hoge(){
modA::hoge()
}
(ランタイムで無限ループするけどそれはいいとして)変換するとこう
// modA.mmm
extern modB:{
hoge: ?
}
fn hoge(){
modB.hoge()
}
//modB.mmm
extern modA:{//実際は型コンストラクタとかで覆って区別できるようにすべき
hoge: ?
}
fn hoge(){
modA.hoge()
}
modA::hoge : 'a ()->'a, modB::hoge : 'a ()->'a
ここまでしか型は決定できないということに
ただまあ、型が決定不能ならその時コンパイルエラーにすればいいだけで、一応型推論自体は無限ループに陥らず完了するのか?
型付け手順
- モジュールA読み込み開始
- モジュールー型宣言マップにファイル名登録
- use modBの解釈開始
- マップに問い合わせ、ファイルがないのでファイル読み込み開始
- use modAの解釈開始
- マップに問い合わせ、ファイルがあったのでそれを参照
Program = FunctionDefinition
|GlobalDeclaration
|ModuleDeclaration
|use ModuleName
ModuleDeclaration = Visibility(stage) { Program }
Pythonのモジュールシステム Python’s Import System - Module object|Regular/Namespace Packages|Finders & Loaders|Relative imports - YouTube
モジュールはオブジェクトであり、__file__
などはモジュールオブジェクトに付属するメンバ変数
Gluonのモジュール Modules - Gluon Documentation
これマクロとして実装してあって、それこそレコードとして出力されるだけなので、分割コンパイルとかモジュール内相互参照とかは全然考慮されてないけど意味論はすっきり