Rustで圏論(6) 小圏・関手・自然変換
0, 1, 2, 3, 小圏
0(対象も射もない圏)、 1(対象が1つと恒等射のみの圏)、 2(対象が2つと、それぞれの恒等射・その間の1つの射からなる圏)、 3(対象が3つとそれぞれの恒等射、射も3つで1つの射が残り2つの射の合成である圏)
をそれぞれRustで表現していこうとしていたのですが、3をやろうとして、ちょっと面倒だな、と感じました。 0/1/2はそれぞれ別の構造体で、それぞれ別のトレイト実装を作っていたわけなのですが、3だと内容の割にコード量が増えたからです。 というわけで、3以外も表現できる一般的な圏を表す構造体を作ることにしました。
#[derive(Default)] struct SmallCat<Object, Arrow> { objects: Vec<Object>, arrows: Vec<Arrow>, }
この機会に、ジェネリックにして、対象も射も好きな型を与えられるようにしました。
こうしたのは、3を作っていて、対象や射に名前を与えたくなったからです。
今まで使っていたCategoryObject
/CategoryArrow
というUnitな構造体では情報が足りないので、
NamedCategoryObject
/NamedCategoryArrow
を作り、それをSmallCat
に与えましょう。
ついでに型エイリアスも宣言します。
#[derive(PartialEq, Default)] struct NamedCategoryObject(&'static str); #[derive(PartialEq, Default)] struct NamedCategoryArrow { name: &'static str, codomain: usize, domain: usize, equals: Vec<Vec<usize>>, } type SmallCat_ = SmallCat::<NamedCategoryObject, NamedCategoryArrow>;
NamedCategoryArrow
は、射の名前以外にも、codomain
/domain
/equals
がいます。
codomain
/domain
があるのは、初回に作った構造体(Category
)と似ています。
equals
は、3で出てくる射の結合を表現するためのものです。
Category
トレイトを実装します。
impl<O: PartialEq> Category for SmallCat<O, NamedCategoryArrow> { type Object = O; type Arrow = NamedCategoryArrow; fn domain(&self, f: &Self::Arrow) -> &Self::Object { &self.objects[f.domain] } fn codomain(&self, f: &Self::Arrow) -> &Self::Object { &self.objects[f.codomain] } fn identity(&self, o: &Self::Object) -> &Self::Arrow { let idx = self.objects.iter().position(|x| x == o).unwrap(); for a in self.arrows.iter() { if a.codomain == idx && a.domain == idx { return a } } panic!(""); } fn composition_internal<'a>(&'a self, f: &'a Self::Arrow, g: &'a Self::Arrow) -> &'a Self::Arrow { let f_idx = self.arrows.iter().position(|x| x == f).unwrap(); let g_idx = self.arrows.iter().position(|x| x == g).unwrap(); for arrow in self.arrows.iter() { for eq in arrow.equals.iter() { if eq[0] == f_idx && eq[1] == g_idx { return arrow; } } } panic!("") } fn objects<'a>(&'a self) -> Box<Iterator<Item=&'a Self::Object> + 'a> { Box::new(self.objects.iter()) } fn arrows<'a>(&'a self) -> Box<Iterator<Item=&'a Self::Arrow> + 'a> { Box::new(self.arrows.iter()) } }
対象や射を区別するのは、Vecのインデックスという仕様にしました。参照にうまくLifetimeを与えられませんでした。
射はNamedCategoryArrow
固定ですが、対象はジェネリックに与えられます。
これを使って3を表現したいわけですが、表記が冗長になるので、以前作ったcat
マクロを改良して使いましょう。
macro_rules! cat { ([ $( $object:ident )* ] [ $( $arrow_name:ident : $codomain:ident -> $domain:ident )* ] [ $( $f1:ident;$f2:ident = $f3:ident )* ]) => ({ let mut objects = vec![]; let mut arrows = vec![]; $( objects.push(NamedCategoryObject(stringify!($object))); let idx = objects.iter().count() - 1; arrows.push(NamedCategoryArrow { name: stringify!($object), codomain: idx, domain: idx, equals: vec![] }); )* let mut equals_list = vec![]; $( equals_list.push( (stringify!($f3), vec![stringify!($f1), stringify!($f2)]) ); )* $( let cod_idx = objects.iter() .position(|x| x.0 == stringify!($codomain)) .unwrap(); let dom_idx = objects.iter() .position(|x| x.0 == stringify!($domain)) .unwrap(); let mut equals = vec![]; for eqs in equals_list.iter() { if eqs.0 == stringify!($arrow_name) { let before_idx = arrows.iter() .position(|x| x.name == eqs.1[0]) .unwrap(); let after_idx = arrows.iter() .position(|x| x.name == eqs.1[1]) .unwrap(); equals.push(vec![before_idx, after_idx]); } } arrows.push(NamedCategoryArrow { name: stringify!($arrow_name), codomain: cod_idx, domain: dom_idx, equals: equals, }); )* SmallCat_ { objects, arrows } }) }
これを使えば、3は以下のように表現できます。 3以外の小圏も同様に定義できます。
let _small_cat = cat!( [X Y Z] [f: X -> Y g: Y -> Z h: X -> Z] [f;g = h] );
関手・自然変換
続いて、関手と自然変換もトレイトを作ってみました。
trait Functor { type DOM: Category; type COD: Category; fn send_objects(object: <Self::DOM as Category>::Object) -> <Self::COD as Category>::Object; fn send_arrows(arrow: <Self::DOM as Category>::Arrow) -> <Self::COD as Category>::Arrow; } trait NaturalTransformation { type C: Category; type D: Category; type F: Functor<DOM=Self::C, COD=Self::D>; type G: Functor<DOM=Self::C, COD=Self::D>; fn components() -> Box<Iterator<Item=<Self::D as Category>::Arrow>>; }
比較的、定義通りに書ける感じがします。 ただ、やはりそれぞれの満たすべき条件を書き表すことはできません。 トレイトの実装者が満たすことになりますね。 まだ何か物足りない気もしますが、具体的に作っていくことで見えてくるでしょう。