Slide 1

Slide 1 text

型クラスと依存型のカルパッチョ、代数的構造を 添えて 2024-01-21 Zli 大LT

Slide 2

Slide 2 text

自己紹介 ActivityPub/Misskey: @[email protected] twitter: @sou7___ アンダーバー3つ 好きな言語: Coq Ruby 学部3年 2

Slide 3

Slide 3 text

みなさん、クラスは使ってますか? 3

Slide 4

Slide 4 text

では、型クラスは使ってますか? 4

Slide 5

Slide 5 text

型クラス (一般的な言語であるHaskellで話を進めます) Haskellには、整数を扱うInteger型、実数を扱うFloat型、有理数を扱うFractional型 などがあります。 「数っぽいもの」全般を扱う処理を書くときに、一回の定義で済ませるために型クラ スを使います。 (C++やJavaでは、インターフェイスや抽象クラスと言われるものに近いです) 5

Slide 6

Slide 6 text

Numクラス class Num a where (+) :: a -> a -> a (-) :: a -> a -> a (*) :: a -> a -> a negate :: a -> a abs :: a -> a signum :: a -> a fromInteger :: Integer -> a 「数っぽいもの」を表すNumクラスは、以下の値や関数を持つような型の抽象を表し ます。 + , - , * の演算子 negate , abs , signum の関数 fromInteger の変換関数 そして、Integer型やFloat型は、Numクラスに属することを宣言(インスタンス化)する と、それらの型をNumクラスとして抽象的に扱えるようになります。 6

Slide 7

Slide 7 text

manhattanDistance :: Num a => (a, a) -> (a, a) -> a manhattanDistance (x1, y1) (x2, y2) = abs (x1 - x2) + abs (y1 - y2) manhattanDistance (1, 2) (3, 4) -- 4 manhattanDistance (1.0, 2.0) (3.0, 4.0) -- 4.0 2つの点のマンハッタン距離を求める関数 manhattanDistance です。 Num型で抽象化して実装しているので、Int型でもFloat型でも使えます。 このように、型を抽象的に扱い、実装を簡単にするのが型クラスの魅力です。 7

Slide 8

Slide 8 text

一般的な言語でのモノイド モノイドとは、集合 と、 くっつける演算( を使って と書く) 任意の に対して、 が成り立つ(結合的) くっつけても何も起こらない値(単位元 ) 任意の に対して、 が成り立つ が定義されている組のことです。[1] Haskellでは以下のように定義します。 -- Monoid型クラスの定義(説明用。現実のGHCのものとはちょっと違うので注意) class Monoid a where (<>) :: a -> a -> a -- 「くっつける」演算 mempty :: a -- 「くっつけても何も起こらない値」 しかし、足りないものがあると思いませんか? 8

Slide 9

Slide 9 text

「約束」をコードに書けない・・・? 普通の言語では、値や関数は書けても、「約束」は書けません。 この問題はどの言語でも起こるのでしょうか? 9

Slide 10

Slide 10 text

いいえ、書けます。Coqならね。 10

Slide 11

Slide 11 text

依存型 Coqは依存型という仕組みを持ち、値を使った型を書けます。例えば、nという値に対 して、 n + 1 = 3 や、「nは偶数」のような型が作れます。 これを使って、モノイドの条件「 は結合的」、「 は単位元」という約束を書くこと ができます。 11

Slide 12

Slide 12 text

Coqでのモノイド Class Monoid (A : Type) : Type := { (* くっつける演算 op *) op : A -> A -> A; (* 単位元 e *) e : A; (* 「opが結合的である」という約束 *) monoid_assoc : forall x y z, op x (op y z) = op (op x y) z; (* 「eが単位元」という約束 *) monoid_e_left : forall x, op e x = x; monoid_e_right : forall x, op x e = x }. 12

Slide 13

Slide 13 text

Instance BoolXorMonoid : Monoid bool := { (* くっつける演算はXOR *) op := xorb; (* 単位元はfalse *) e := false; (* XORは結合的という約束を与える(標準ライブラリで証明済み) *) monoid_assoc := fun a b c => eq_sym (xorb_assoc a b c); (* falseは単位元という約束を与える(標準ライブラリで証明済み) *) monoid_e_left := xorb_false_l; monoid_e_right := xorb_false_r }. boolのXOR演算はモノイドの性質を満たすので、それらをモノイドのインスタンスと して扱えるようになります。 13

Slide 14

Slide 14 text

継承 型クラスにも継承の仕組みがあります。Coqでは、群がモノイドであるといったふう に、あるクラスがスーパークラスの要素を要求する形で行われます。 群は、モノイドの要素に加えて、任意の要素 に対して、逆元 が存在するという条 件が追加されます。 Class Group A (M : Monoid A) : Type := { group_inv : forall x, exists xi, op xi x = e /\ op x xi = e; }. ここでは、モノイドを拡張して群を定義してみました。モノイドの諸性質に加え、逆 源に関する公理を追加することで群を定義します。 14

Slide 15

Slide 15 text

Lemma xorb_inv : forall x, exists xi, xorb xi x = false /\ xorb x xi = false. Proof. move=> x. exists x. split; apply xorb_nilpotent. Qed. 補題 xorb_inv を証明、つまり「約束」が守られることを確認します。 (* boolとXorの群 *) Instance BoolXorGroup : Group BoolXorMonoid := { group_inv := xorb_inv }. インスタンス化する際にその「約束」を渡すことで、boolとXORを群として扱えるよ うになります。 そして型クラスの仕組みによって、他の群を前提としたプログラムや証明に対して boolとXOR演算を適用できます。 15

Slide 16

Slide 16 text

型クラスを使った証明 モノイドの単位元が1つだけ存在することの証明です。 を証明しています。 Theorem monoid_exists_unique_e A (M : Monoid A) : forall e', (forall x, op e' x = x) -> e' = e. Proof. move=> e' H1. rewrite -(monoid_e_right e'). by rewrite -{2}(H1 e). Qed. monoid_e_right ( が単位元という約束)を使っているのが見えます。 16

Slide 17

Slide 17 text

群の逆元がただ1つ存在することの証明です。 exists! で1つだけ存在することを表し ます。 Theorem group_exists_unique_inv A (M : Monoid A) (G : Group M) : forall x, exists! xi, op xi x = e /\ op x xi = e. Proof. move=> x. rewrite -(unique_existence _). split. - case (group_inv x) => xi [Hxil Hxir]. exists xi. by split. - move=> a b [Hal Har] [Hbl Hbr]. rewrite -(monoid_e_right a) -Hbr. rewrite monoid_assoc. by rewrite Hal monoid_e_left. Qed. group_inv (逆元が存在するという約束)、 monoid_assoc (演算が結合的という約束)な ど、様々な約束を用いて証明しているのがわかります。 17

Slide 18

Slide 18 text

群の逆元がただ1つ存在するので、決定性の値取り出し公理を用いてinv関数を定義し ます。また、inv関数を使った という性質も証明します。 Definition inv A (M : Monoid A) (G : Group M) : A -> A := fun x => proj1_sig (constructive_definite_description _ (group_exists_unique_inv G x)). Lemma inv_sort A (M : Monoid A) (G : Group M) : forall x xi, xi = inv G x <-> op xi x = e /\ op x xi = e. Proof. move=> x xi. unfold inv. move: (constructive_definite_description _ (group_exists_unique_inv G x)) => He. split. - case (proj2_sig He) => [Hel Her] ->. by split. - move => Hxe. move: (group_exists_unique_inv G x). rewrite -unique_existence => [[_ Huniq]]. apply Huniq => //. by apply (proj2_sig He). Qed. Lemma op_inv_left A (M : Monoid A) (G : Group M) : forall x, op (inv G x) x = e. Proof. move=> x. by apply (inv_sort G x (inv G x)). Qed. Lemma op_inv_right A (M : Monoid A) (G : Group M) : forall x, op x (inv G x) = e. Proof. move=> x. by apply (inv_sort G x (inv G x)). Qed. 18

Slide 19

Slide 19 text

先程定義したinv関数とその性質を使って、 という性質を証明します。 (時間があればライブプルーフィング) 19

Slide 20

Slide 20 text

まとめ 型クラスは型を抽象化して扱える。同じような特徴を持つ型をまとめて実装でき る 一般的な言語では「約束」を扱えないが、Coqでは扱える モノイドや群といった数学的対象も、Coqなら楽しく実装できる 20

Slide 21

Slide 21 text

参考文献/ソースコード 「Haskellerのためのモノイド完全ガイド」 https://blog.miz- ar.info/2019/02/monoid-for-haskellers/ mod_poppo モノイドの定義部分を引用 「Type classes」Coq公式ドキュメント https://coq.inria.fr/doc/V8.17.1/refman/addendum/type-classes.html? highlight=class#coq:cmd.Class Coqのクラスの例(モノイドと群) ソースコード https://gist.github.com/soukouki/c52e7c2d4e615029d66696fd74afb1f0 21