Rustで圏論(4) ゼロ圏を作る

対象の族と射の族の型を変更した

今回定義している圏のトレイトCategoryの中には、対象のHashSet(C0)と射のHashSet(C1)を返すメソッドが定義されていました。今回、思うところもあってHashSetからIteratorに変更しました。気分の問題といえばそこまでですが、C0もC1も集合になるとは限らないので。Iteratorであれば、色々便利そうだな、という目論見も含めての変更です。

トレイト内でimpl traitは使えない

impl traitが安定化してから初めてコードを書いているので、嬉しくてさっそく、圏のトレイトの定義に使ってみようとしたのですが、inherentな関数じゃないと使えません、とコンパイラに怒られてしまいました。しかたなく、今まで通りBox<Iterator<Item>>にすることで落ち着きました。

trait Category {
    type Object: PartialEq;
    type Arrow: PartialEq;

    fn domain(&self, f: &Self::Arrow) -> Self::Object;
    fn codomain(&self, f: &Self::Arrow) -> Self::Object;
    fn identity(&self, a: Self::Object) -> Self::Arrow;
    fn composition(&self, f: Self::Arrow, g: Self::Arrow) -> Option<Self::Arrow> {
        if self.codomain(&f) != self.domain(&g) {
            None
        } else {
            Some(self.composition_internal(&f, &g))
        }
    }
    fn composition_internal(&self, f: &Self::Arrow, g: &Self::Arrow) -> Self::Arrow;
    fn ci(&self, f: &Self::Arrow, g: &Self::Arrow) -> Self::Arrow {
        self.composition_internal(f, g)
    }

    fn objects() -> Box<Iterator<Item=Self::Object>>;
    fn arrows() -> Box<Iterator<Item=Self::Arrow>>;

    fn associativity(&self, f: Self::Arrow, g: Self::Arrow, k: Self::Arrow) -> bool {
        self.ci(&self.ci(&f, &g), &k) == self.ci(&f, &self.ci(&g, &k))
    }
    fn unit_law_domain(&self, o: Self::Object, f: Self::Arrow) -> bool {
        self.ci(&self.identity(o), &f) == f
    }
    fn unit_law_codmain(&self, o: Self::Object, f: Self::Arrow) -> bool {
        self.ci(&f, &self.identity(o)) == f
    }
}

まずはゼロ圏を作ってみる

「ゼロ圏」という呼び方が正しいのか自信がないです(書籍にはローマ数字の0がそのまま記号として使われている印象)。ともかく、implするのが簡単そうなので、ここから始めてみることにします。

impl Category for Zero {
    type Object = ();
    type Arrow = ();

    fn domain(&self, _f: &()) {}
    fn codomain(&self, _f: &()) {}
    fn identity(&self, _a: ()) {}
    fn composition_internal(&self, _f: &(), _g: &()) {}
    fn objects() -> Box<Iterator<Item=()>> { Box::new(empty()) }
    fn arrows() -> Box<Iterator<Item=()>> { Box::new(empty()) }
}

std::iter::emptyを初めて使ってみました。空のイテレータって中身がないのでこう書けるのは煩雑にならなくて便利ですね。ObjectArrow()にしたのは、実装の見た目がスッキリするから以上の理由はありません。Unit自体はぜろというより1(対象が1つと恒等射のみからなる圏)ですけどね、イメージとしては。まあいっか。