前回 の一番最後にラスト草案として書いたものを採用して実装した.
type T = A | B | C;
はおよそ \[T = A \cup B \cup C\] を表している. そしてまた, \(T\) は恒等射 \(T \to T\) を表す. あるいは, もっと実際的には \(A \to T\) と \(B \to T\) と \(C \to T\) の3つの包含写像を1つにマージしてしまったような関数.
これは暗黙的なキャストとしては 実行されない.
type T = Nat | String;
let t: T = 1; // ERROR!
let t = T(1); // OK
有用性という意味では \(A,B,C\) は排反であって \(T\) がちょうど直和になってるくらいがいいけど, 実際はただの和集合であって何も気にしない. 特に cumin は意味を JSON で与える. 型 A
がついた値 \(a\) の意味が \(\overline{a}\) のとき, \(T(a)\) の意味は尚も \(\overline{a}\) である.
パーサーコンビネータライブラリとして combine 4.4.0 を使ってたけど nom 6 に変更した. 使い心地としては combine の方が気の利いたAPIが細やかに用意されていて気に入っていた. 例えば 1 + 2 + 3
みたい数式のパースには chainl1 がちょうど便利だ. nom でコレに一番近いのが fold_many1 だけど, そのものじゃないし, コレ何が便利なの? ていうかそういう問題じゃなくて, combine を使ったパーサーのコンパイルは平気で三分程度掛かる. 再帰をするパーサー関数は parser!{}
マクロに包まないと行けないのでその中では rustfmt が効かない. といった実用上の不便が大きかった. nom に置き換えてコンパイルは10秒で終わるし(ライブラリのコンパイルを全部含めても50秒あれば終わるし) マクロを一切使わずにコード量は削減した.
そろそろさすがに満を持して何でもやれるという気持ちになってきた. これからどうしよう.
モジュールには文だけを書くことが出来る. struct や enum や type の定義だけを別のファイルに切り出してモジュールとし, これを use 文で読み込める. use も文なのでモジュールからまた別のモジュールを読める.
名前が衝突していた場合に, 現状, 何も警告をしないし, いずれかで上書きされるだけである. 単に処理系がその名前に出会った順番で一番最後のもので上書きされるはずだが, 特に何かを保証してるわけではない.
// mod.cumin
struct P {
x: Int,
y: Int,
}
use "mod.cumin";
[
P(0, 1),
P(2, 3),
]