Zigの既存ポリモーフィズムと課題:動的インターフェースとcomptimeダックタイピング

Zig言語に「Traits」を導入する新たな慣習の提案—コンパイル時型契約で抽象化を強化のイメージ画像

Zig言語では、これまで主に2つの方法でポリモーフィズムを実現してきました。

動的なインターフェースは実行時ポリモーフィズムを、comptimeダックタイピングは静的ポリモーフィズムを提供してきました。

一つは動的インターフェースで、不透明なポインタや仮想テーブル、動的ディスパッチを用いて実行時ポリモーフィズムを実現します。これは他の言語で見慣れた型契約を提供しますが、実行時にモジュール性を持たせる設計のため、コンパイラの最適化に制約が生じ、パフォーマンスオーバーヘッドが発生する可能性があります。

間接参照が多いため、モノモルフィックなコードに適用できる最適化が効きにくい場合があるようです。(Lobsters)

もう一つはcomptimeダックタイピングです。これはanytype引数や型引数Tを用いて、コンパイル時に型のリフレクションや型が持つメソッドの仮定に基づいてモノモルフィズムを実現します。C++のテンプレートやCのマクロに似ていますが、型契約の違反がコンパイル時に捕捉されるため、実行時ダックタイピングよりも安全性が高いと言えます。

しかし、呼び出し元と呼び出し側で型契約を文書化し、それに従う注意が必要です。(Lobsters)

新しい抽象化の提案:コンパイル時Trait

Zig言語に「Traits」を導入する新たな慣習の提案—コンパイル時型契約で抽象化を強化のイメージ画像

主記事の著者Axel Magnussen氏は、これらの既存のアプローチに加えて、RustのTraitやHaskellの型クラスに似た抽象化をZigのユーザーランドで実現する新しい慣習を提案しています。

Traitは、特定の型に関する関連関数を含むコンパイル時構造体として定義されます。

これは「Trait」と名付けられ、コンパイル時構造体として関連する関数を特定の型に対してまとめるものです。このアプローチは外部ライブラリや特別な仕組みを必要とせず、バニラのZigに非常にうまく適合すると述べられています。

関数型プログラミングの世界、特にOCamlから着想を得ているとのことです。(Lobsters)

Traitの実装例:Reducer Trait

記事では、Reducer Traitを例にその実装方法が示されています。Reducer Traitは、空の値(empty)と結合操作(combine)を持つ型に対して、畳み込み(reduction)操作を提供するものです。

/// T forms a Reducer under an empty value and a combine operation
fn Reducer(T: type) type {
    return struct {
        empty: T,
        combine: fn (T, T) T,
        fn reduce(self: @This(), xs: []const T) T {
            var accum = self.empty;
            for (xs) |x| accum = self.combine(accum, x);
            return accum;
        }
    };
}

このReducer Traitを用いて、i32型に対する和のReducerと積のReducerを実装する例も示されています。

Traitの実装 sum_trait (和) product_trait (積)
empty値 0 1
combine操作 x + y x * y

このように、Traitは特定の型の振る舞いを抽象化し、再利用可能な形で定義できることがわかります。コンパイル時に型契約を検証できるため、comptimeダックタイピングよりもより厳密な型安全性を確保しつつ、実行時のオーバーヘッドを避けることが可能です。

エンジニア目線で見ると:コンパイル時型契約は表現力と安全性を両立する

この「Trait」の提案は、Zigのシンプルさを保ちつつ、より高度な抽象化と型安全性を求める開発者にとって非常に魅力的な選択肢となり得るのではないでしょうか。特に、パフォーマンスがクリティカルな部分ではcomptimeダックタイピングが多用されますが、その型契約は慣習に頼る部分が大きく、バグが入り込む余地がありました。今回のTraitは、コンパイル時構造体という形で明示的な型契約を定義できるため、この曖昧さを大きく減らせると感じます。

私の個人プロダクトでもPythonとTypeScriptをメインで使っていますが、それぞれ静的型付け(TypeScript)と動的型付け(Python)の間で型安全性のバランスに常に悩まされます。ZigのこのTraitは、Rustのそれと同様に、コンパイル時に型安全性を担保しつつ、動的ディスパッチによるオーバーヘッドを避けるという、両者の良いとこ取りを目指しているように見えます。GCP上のCloud Runなどでマイクロサービスを動かす際にも、起動時間の短縮や実行時の低レイテンシーが求められるため、このようなコンパイル時最適化の恩恵は非常に大きいでしょう。

特に、comptimeを積極的に活用するZigの設計思想と非常にマッチしていると感じます。Pythonバインディングなどがあれば、既存のシステムへの組み込みも検討したいです。