自作言語に型推論を
つけたい話
社内用
-
名前: r-chaser53
-
GitHub: https://github.com/rchaser53
-
Twitter: https://twitter.com/rChaser53
-
所属: LINE株式会社
-
何やってる人?
-
自作言語作りたい人(経験1年くらい)
-
rustfmtのお手伝いしてます
-
誰?
-
自作言語でfizzbuzzできるとこまでやった
-
LLVM-IRが最終出力産物
-
発生するエラーのハンドリングが厳しい
-
型を手で入力させるも非常に辛い
背景
というわけで
型推論を実装してみよう
ついでに色々と挑戦してみよう
成果物
型推論 is 何
文脈から型を推論すること
// TypeScriptのファイルと考えてほしい
const abc = 13; // number
const strFunc = (a) => {
return a + "hoge" // string
}; // strFuncは引数stringを取り、戻り値stringを取る関数
-
手製のLexer, Parserからの脱却
-
宣言時に型を明示させない
-
型関連の知識の強化
今回やりたかったこと
- Lexer: 字句解析
- 文字列を1文字ずつ分析していって字句を作る
- ex. 123, "abc", true, false
- Parser: 構文解析
- Lexerが作ったものを受け取りASTを作る
- AST (Abstract Syntax Tree: 抽象構文木)
- ソースファイルを扱いやすいように木構造にした物
- 型解析やLint、Fomarterなど色々なことに使われる
- V言語(笑)みたいにASTを作らない言語もある
Lexer, Parserって?
https://ts-ast-viewer.com/ 使ってASTのサンプル見せること
-
combineを使用
-
combine = parser combinator
-
慣れるまで時間かかったが便利
-
前回までは構文の修正にかなり時間がかかったが、cominbeにより相当短縮できた
1. 手製のLexer, Parserからの脱却
-
制限はあるがある程度は作れた
-
引数、戻り値の型は省略できる
-
再帰は大丈夫
-
let多相性は作れた?
-
2. 宣言時に型を明示させない
-
demo1
-
引数、戻り値の型は省略できる
-
2. 宣言時に型を明示させない
-
demo2
-
再帰は大丈夫
-
2. 宣言時に型を明示させない
-
demo3
-
let多相性は作れた?
-
2. 宣言時に型を明示させない
多相性 is 何?
-
1つのコードで複数の型を扱えるようにした物
-
パラメータ多相(genericsとか)
-
アドホック多相(オーバーロードとか)
- let多相性(今回実装した物)
-
-
逆に1つの型しか扱えないものを単相型という
let多相性をもうちょい
-
多相性をトップレベルのlet束縛に限定する
-
演算子や式の構造から型を判断する
-
判断した結果を保存する
-
不明な変数は残す
-
不明な変数はワイルドカードのように扱う
-
整合性が取れなかったらエラー
実装の方針
-
基本的に上から順に式の解決をしていく
-
変数などの型情報は保存するが 関数は毎回型を解析している
-
「fn (a) { return a }」みたいなケースのため
多相と再帰
let abc = fn(a) { return a; } in (
abc(22); // ここで「引数intをとり、intを返す関数と保存してしまうと
abc("aa"); // ここで引数stringが渡されるのでエラーになる(本来ならばOKのはず)
)
このやり方だと
再帰の場合止まらない…
-
呼び出し元の関数の型情報を保存しておき、同じ関数から呼び出されていた場合使用する
-
このやり方だと相互再帰は止まらない
多相と再帰
// 相互再帰
let abc = fn(a) {
if (a <10) {
def(a+1);
}
}
def = fn(b) {
if (b<10) {
abc(b+1);
}
}
in (
abc(0); // stack over flow!
)
呼び出すパターンを保存する?
良い方法が思いつかないので別途調査
-
呼び出し元の関数の型情報を保存しておき、同じ関数から呼び出されていた場合使用する
-
このやり方だと相互再帰は止まらない
どうするべきだった?
// 相互再帰
let abc = fn(a) {
if (a <10) {
def(a+1);
}
}
def = fn(b) {
if (b<10) {
abc(b+1);
}
}
in (
abc(0); // stack over flow!
)
呼び出すパターンを保存する?
良い方法が思いつかないので別途調査
-
健全性、完全性の保証
-
相互再帰
-
エラーメッセージの正確な表示
-
構文エラーのエラーメッセージ表示
-
hashmapの型推論の完全なサポート
今回できなかったこと
-
構文や型を明確に定義できていない
-
となると健全性も完全性もクソもない
-
定義を明確にするために何かのサブタイプにを作り、それに対して型推論機を作った方が勉強としては良さそう
-
健全性、完全性
正しく型付けされた項は行き詰まりにならず、次の状態のどちらかである
-
値である
-
評価規則によって評価を進められ、評価後も正しく型付けされている
- a+a みたいなイメージ
健全性(安全性)
型システム入門には以上のように書かれているが難しい…
型付けできたら正しく動く
くらいの認識で良いと思う(合ってるかは怪しい)
正しいプログラムは必ず型が推論できる
-
型システム入門に定義らしい定義が見つからなかった
-
完全性を保つのは難しいので、健全性だけ保つケースが多い
完全性
-
読むと思考停止していた型関連の文章と 戦えるようにはなった
-
大量に出てきた単語や公式もある程度は 理解できたと思う
-
Rustの型周りの実装の理解が進むはず…
3. 型関連の知識の強化
-
楽しい
-
言語仕様をもう少し決めてから作るべき
-
ケースが多い。開発手順から改善するべき
-
ex. モンキーテスト=> バグ発見 => テスト追加
-
-
文章だけではイメージが湧かないので もっとソースを読むべき
-
PRでも投げながら学習していきたいと思う
まとめや感想
参考資料
ご静聴ありがとうございました
社内用
By rchaser53
社内用
ASTとかJser向けとか色々直す
- 1,147