今月の活動 (ミローネ言語)
- 前回 (2021-09-30) https://vain0x.github.io/blog/2021-09-30/diary/
ミローネ言語
https://github.com/vain0x/milone-lang
- リリース: Release v0.4.0 · vain0x/milone-lang
- 前回書いたとおり、Windowsでも動くようになったのでmainブランチにマージできた
- インストールスクリプトを整理した
インストールスクリプトを F# で書くことでインストールが楽になった(訂正: 記憶違い。配布パッケージを作る部分を F# で書いたのと混同していた。インストールにはパッケージに同梱しているシェルスクリプトを使う。)
ML: 単相化の再設計
単相化の処理が重いから並列化しやすいように書き直したら、並列化する必要がないぐらい速くなった - Twitter @vain0x
- 以前の方法ではプログラム全体を何周も処理していた
- セルフコンパイルでは10回以上処理していた
- 以前の方法は、プログラム全体を見て、多相関数の使用箇所のうち、使用箇所の型が型変数を含まないようなものをすべて探して、その定義箇所に単相化インスタンスを生成して、使用箇所をその単相化インスタンスの使用に差し替える、というのを何も起こらなくなるまで繰り返していた (いわゆる不動点アルゴリズム)
- 今回は使用箇所の探索と単相化インスタンスの生成を分離した
- ワークリストという、式からなるリストを用意する
- はじめにプログラム全体をワークリストに入れる
- ワークリストに入っている式を探索して、多相関数の単相な使用を列挙する
- 見つかった分の単相化インスタンスを生成する
- 生成された単相化インスタンスの本体をワークリストに入れる
- 以上をワークリストが空になるまで繰り返す
- 最後にプログラム全体を変形して、多相関数の使用箇所を単相化インスタンスに差し替える
- 探索範囲がとても狭くなった
- 1つの式を1回だけ訪問する
- 変換作業も1回だけでよくなった
- セルフコンパイルで単相化のパスが300ms→70msまで縮んだ
ML: 自動生成する関数名のつけかたを改善したらコンパイル時間が減った
- いままで自動で生成する関数のほとんどに
fun
という名前をつけていた- 匿名関数 (
fun _ -> ...
) や、部分適用を解決する際に発生する関数など
- 匿名関数 (
- Cのコードを生成する際に適当な連番をつけることで、名前の衝突を回避していた
- コードの一部を変更して再コンパイルしたとき、連番がずれることで、生成されるCのファイルの多くに差分が出てしまっていた
- 文脈に沿った名前をつけるようにした
- 例えばモジュールMの中の、関数fの中の
fun _ ->
にはM_f_fun
とか
- 例えばモジュールMの中の、関数fの中の
- 結果として、変更したモジュールに対応するファイルだけ再コンパイルされることが多くなった
ML: ファイルシステムAPIの設計
- ファイルシステムのAPIを設計するときファイルパスをどうするか
- Linuxのファイルパスは、単にファイル名を連ねたものになっている (
/foo/bar/baz
や./foo/bar
) - Windowsではパスにプリフィックスという部分があり、ドライブレターなどを指定できる
- LinuxとWindowsだけサポートするとき、パスがプリフィックスをもつかもしれないというのはWindows特有の関心事であり、捨象したい
- WindowsではパスのエンコーディングがUTF-8ではないが、必要に応じて変換すればいいから問題ない
ML: オブジェクトのポインタからフィールドのポインタを取る構文
type Buf = { Len: int; ... }
val buf: nativeptr<Buf>
現状の構文で buf.Len
を1増やすにはこう書く:
buf |> write { read buf with Len = (read buf).Len + 1 }
- 冗長すぎる
- しかもbuf全体を書き換えてしまう
- 一方、Cなら
buf->Len++
で済む - 問題は
nativeptr<Buf>
からnativeptr<int>
(Lenへのポインタ) に絞る方法がないこと- フィールドの選択が絡むのでライブラリ関数では対処できない
- それがあったら例えば
&(*buf).Len |> increment
でいい - bufがポインタのとき
buf.Len
を(*buf).Len
と解釈する案はある- ドットで自動的に脱参照する
- ポインタを
&buf.Len
でとることにすれば、&but.Len |> increment
などと書ける - いままで値を表す式しかなかったところに、ストレージを表す式が出てくる
- 低レイヤの世界観になっている
- 型検査を複雑にしたくないのでためらうのもある
- 低レイヤ専用の言語を作って相互運用というのも1つの方法かもしれない
ML: 余談: 基本ブロック内のヒープをまとめる案
- 同一基本ブロックに出現するヒープ確保は1回にまとめられる
- GCによるメモリ管理だと不用意にまとめるとメモリリークを引き起こすが、動的リージョンベースなら問題ない (そういう意味でははじめからリークしているともいえる)
- ざっと見積もった感じ、セルフコンパイルではヒープ確保が7%ぐらい減少しそう
その他
- 開発中のLSPサーバーを自動でリロードする機能をサンプルにした:
- vain0x/vscode-auto-reload-lsp-server: Example of auto-reload LSP server on rebuild
- hsp3-analyzer-miniやmilone-langで使っていて、かなり便利
- できたら再利用できるようにしたい
- ソフトウェアの書き直しに関する記事を読んだ:
- 書き直しのはなし- Message Passing (書かれたのは2021-08〜2021-09ごろ)
-
いま目の前にあるこのコード、ついカッとなって書き直しちゃダメ?
- 「一周目をちゃんとやる」「失敗パターン」のところが印象深い
- 私の例でいえばknowbugはv1→v2で書き直しに近い変更をした
- Rustで書き直したいと日々思っている
- hsp3-analyzer-miniも公式のパーサは使わずにゼロから再実装という点では書き直し
- ミローネ言語も現行の設計に問題があると感じていて書き直したいので、参考にする
- ついにVにハマってしまいつつある
- リズムゲームがらみでVとの接触が増えたため
- 時間が溶ける