RustのContext Generics Programming Patternのメモ
cgp crateのドキュメントよりもそことは別に書いてあるCGP bookのほうが詳しく書いてある。 Haskeller向けに一言でいうとHas type classパターンとmtl-styleを使ったRIOパターンでの設計手法をRustの限られた型システムの自由度の中で再現したもの。Scalaの(Minimal?)Cakeパターンに近い(そういう話もされてる)が、Has type classパターンとRIOモナドを使ったconfigデータの引き回しとかもできるのでイコールじゃないよみたいなことを言ってるっぽい(Scalaに詳しくないのでそこらへんはよくわからん)。MonadもHKTに対するtraitの定義もできないので、陽にContext型を引き回す必要があるが、method chainがあるのでそこまで辛くないみたいな感じになってる。tagless finalとかができるわけではないので、Cakeパターン同様に無限のボイラープレートが必要になる。cgp crateはそのボイラープレートをなんとかしてくれるマクロ群というのが実態に近い。 (RIOパターン&mtl-styleがそうであるように)メリットとして(cgp crateを使えば)かなり簡単に疎結合な抽象を生やしたり消したりできること。これをやりたいがためにありとあらゆる言語は苦しんでる(マクロでメタプロしたり部分型を乱用したりの地獄)ので、できるだけ偉いという話がある。よくあるのはDIとかいうやつももちろんできる。しかし、動的に抽象に対する実装を入れ替えたりするにはContextを動的に切り替える機構が必要でそこまではサポートしてくれないので悲しいね。
zero-costを謳っているがもちろんボイラープレートが無限に生成されるためバイナリサイズは無限になる。
用語
Context
引き回す型のこと。基本的にはContextは多相にするので、テストの場合はそのテスト用のContextを使ったりする。基本的に各関数なりメソッドは多相なContextに対して必要なメソッドだけtrait境界で言及して使うみたいなよくあるスタイルになる。
code: context.rs
fn greet<Context>(context: &Context)
where
Context: HasName
{
println!("Hello, {}", context.name());
}
Consumer
上記のコード例のようなContextのtrait methodを使うような関数/メソッドのこと。Contextを消費するというイメージらしい。traitの場合は CanHoge みたいな名前にしてる。
Provider
Contextのtraitメソッドの実装を与えるもの。Providerも他のtraitのConsumeしてもよいのがミソ。これで抽象のレイヤリングみたいなのができる。ProviderでもContextは多相にしておきたいのでblanket implementationsにしたいけど素直にやると一つのmethodに対して複数のProviderが定義できないし、blanket implementationの制約が漏れ出たりするのでウルテクが必要。ウルテクのせいで無限にボイラープレートが増える。それをなんとかしてくれるマクロを提供してくれるのがcgp crate。ウルテクの詳細についてはCGP bookに書いてある。
謎
CGP macroで定義した時にProvider trait内でConsumer呼べるか?
多分いける
Contextの動的な切り替えはかなりめんどう
Componentの移譲関係は静的なのが前提
macroを書く
動的に実装を変える場合はContextを差し替える必要があるが......
動的に変わりうる部分の全パターンの組み合わせのComponentを定義する必要がある。
設定ファイルによってProviderが変わりうる場合地獄が生まれる
Data StoreがファイルシステムかS3かRDBか、ハッシュなどの計算アルゴリズムの差し替え、依存するWeb APIの切り替えなど意外と世の中設定ファイルで動的に決まる仕様は多い。
Rustのtrait解決の仕様的にはそれはそうって感じなので、traitオブジェクトと組み合わせればなんとかなるかも
気持ち
祝福(MonadやEffect System)なき褪せ人(Rustacean)には福音だけどやっぱ動的なContextやComponentの差し替えができないのは辛いね。嬉しさ半減。