Agda2のcodata定義
Agda2では、dataで定義したデータ型は有限で、それを利用する再帰関数も停止性が証明できないといけないけれど、codataは、停止しない再帰関数を記述することができます。
定義は、ラベルがcodataである以外、dataと同じです。以下のStream Aは、List Aと同じ構造ですが、有限である必要がないので、nil相当のコンストラクタはcodataには不要です。むしろnil相当が存在しないことで、このcodataが必ず無限であることをあらわしています。
codata Stream (A : Set) : Set where stream : A -> Stream A -> Stream A {- <=> data List (A : Set) : Set where nil : List A cons : A -> List A -> List A -}
data同様に、普通の関数の引数や戻り値として使えます。
top : {A : Set} -> Stream A -> A top (stream h _) = h next : {A : Set} -> Stream A -> Stream A next (stream _ ts) = ts
無限のcodataを扱う無限の再帰関数を記述するときは、"="のかわりに"~"で右辺を定義します。
gen : {A : Set} -> A -> Stream A gen a ~ stream a (gen a) map : {A B : Set} -> (A -> B) -> Stream A -> Stream B map f (stream h ts) ~ stream (f h) (map f ts)
- gen aは、無限にaが取り出せるStreamを作る関数
- map f s は、無限Streamのsの各要素にfをかけたStreamを作る関数
こういった関数の定義に"="を使うと、data同様停止性チェックが働き、停止しないよエラーがでます。genの定義の"~"を"="に変えると以下のエラーが出ました:
[CoData.gen] does NOT termination check
逆に言うと、"~"を使う定義は、無限のデータなので戻り値の型はcodataである必要があります。
data List (A : Set) : Set where [] : List A _::_ : A -> List A -> List A mapl : {A B : Set} -> (A -> B) -> Stream A -> List B mapl f (stream h ts) ~ (f h) :: (mapl f ts)
戻り値がdataだと、"~"であっても非停止エラーがでます:
[CoData.mapl] does NOT termination check
前回のNatやVecも加えて、いくつか基礎的な関数を作ることができるでしょう:
data Nat : Set where zero : Nat succ : Nat -> Nat data Vec (A : Set) : Nat -> Set where nil : Vec A zero cons : {n : Nat} -> A -> Vec A n -> Vec A (succ n) take : {A : Set} -> (n : Nat) -> Stream A -> Vec A n take zero _ = nil take (succ n) (stream h ts) = cons h (take n ts) nats : Nat -> Stream Nat nats n ~ stream n (nats (succ n))
- take n sは、sからn個のデータをVecにして取り出す関数
- nats nは、n,n+1,n+2,...というStreamを作る関数
codataもdata同様、コンストラクタの引数には、自身が引数になる関数を、使うことはできないようです:
codata Contra : Set where contra : (Contra -> Nat) -> Contra
このエラー
negatively in the constructor .CoData.contra of Contra
証明では以下のように使えます。
data eq {A : Set} (x : A) : A -> Set where refl : eq x x gen_eq_next_gen : {A : Set}{a : A} -> eq (gen a) (next (gen a)) gen_eq_next_gen = refl
構造比較なので、gen aも next (gen a)も同型と判定され、チェックが通ります。
式の形はターミナルでチェックできます。
Main> gen zero gen zero Main> next (gen zero) gen zero Main>
put it together
CoData.agda
module CoData where codata Stream (A : Set) : Set where stream : A -> Stream A -> Stream A top : {A : Set} -> Stream A -> A top (stream h _) = h next : {A : Set} -> Stream A -> Stream A next (stream _ ts) = ts {- codata infinite recursive definition is "~", not "=" (no termination check) -} gen : {A : Set} -> A -> Stream A gen a ~ stream a (gen a) map : {A B : Set} -> (A -> B) -> Stream A -> Stream B map f (stream h ts) ~ stream (f h) (map f ts) {- data List (A : Set) : Set where [] : List A _::_ : A -> List A -> List A mapl : {A B : Set} -> (A -> B) -> Stream A -> List B mapl f (stream h ts) ~ (f h) :: (mapl f ts) -} {- stream with nat, vec -} data Nat : Set where zero : Nat succ : Nat -> Nat data Vec (A : Set) : Nat -> Set where nil : Vec A zero cons : {n : Nat} -> A -> Vec A n -> Vec A (succ n) take : {A : Set} -> (n : Nat) -> Stream A -> Vec A n take zero _ = nil take (succ n) (stream h ts) = cons h (take n ts) nats : Nat -> Stream Nat nats n ~ stream n (nats (succ n)) {- cannot create contra-variant argument data codata Contra : Set where contra : (Contra -> Nat) -> Contra -} {- proof -} data eq {A : Set} (x : A) : A -> Set where refl : eq x x gen_eq_next_gen : {A : Set}{a : A} -> eq (gen a) (next (gen a)) gen_eq_next_gen = refl