G8c: Punkty stałe funktorów wielomianowych [TODO]


From Typonomikon Require Import G8b.

Najmniejsze i największe punkty stałe funktorów

TODO: To tylko wstępna notatka, trzeba porządnie wytłumaczyć co to punkt TODO: stały i tak dalej. Zamknąć też Mu i Nu w osobny moduł.

From Typonomikon Require Import D2z D5 F3 F4.

Unset Positivity Checking.

Jeżeli wyłączymy sprawdzanie ścisłej pozytywności możemy zdefiniować rodziny typów Mu i Nu (po polsku wymawiane "mi" i "ni"), które reprezentują najmniejszy i największy punkt stały funktora. Pisząc "funktor" mam na myśli byt o typie Type -> Type, zaś jego punkt stały to typ X, który spełnia F X = X.

Inductive Mu (F : Type -> Type) : Type :=
| In : F (Mu F) -> Mu F.

Arguments In {F} _.

Spójrzmy na Mu. Jego jedyny konstruktor In ustanawia izomorfizm między typami F (Mu F) oraz Mu F, co oznacza, że Mu F faktycznie jest punktem stałym funktora F. Mu jest typem induktywnym, co gwarantuje nam, że ten punkt stały jest najmniejszym punktem stałym.

CoInductive Nu (F : Type -> Type) : Type :=
{
  Out : F (Nu F);
}.

Arguments Out {F} _.

Dla Nu wszystko działa dokładnie dualnie. Pole Out ustanawia izomorfizm między Nu F i F (Nu F), co czyni Nu F punktem stałym funktora F. Ponieważ Nu jest koinduktywne, ten punkt stały jest największym punktem stałym.

Set Positivity Checking.

Jak możemy użyć Mu i Nu do definiowania odpowiednio typów induktywnych i koinduktywnych? To proste: wystarczy zdefiniować funktor i zaaplikować do niego Mu lub Nu. Do definiowania funktorów najprościej zaś jest użyć mechanizmu definiowania typów induktywnych.

Inductive ListR (A X : Type) : Type :=
| NilR : ListR A X
| ConsR : A -> X -> ListR A X.

Arguments NilR {A X}.
Arguments ConsR {A X} _ _.

Typ ListR reprezentuje funktor wielomianowy F(X) = 1 + A * X. Jest to funktor bazowy typów list i CoList, czyli że list = Mu (ListR A), a CoList = Nu (ListR A).
Przyjrzyjmy się ze szczegółami, jak to dokładnie działa.

Module List.

Definition List (A : Type) : Type := Mu (ListR A).

Zdefiniowanie typu List jest banalne...

Unset Guard Checking.
Lemma List_ind' :
  forall
    {A : Type} (P : List A -> Prop)
    (HNil : P (In NilR))
    (HCons : forall (h : A) (t : List A), P t -> P (In (ConsR h t)))
    (l : List A), P l.
Proof.
  fix IH 5.
  destruct l as [[| h t]].
    exact HNil.
    apply HCons, IH; assumption.
Defined.
Set Guard Checking.

... ale żeby udowodnić dla typu List regułę indukcji musimy wyłączyć termination checker. Powód tego jest prosty: Mu nie jest typem ściśle pozytywnym, a zatem Coq nie widzi, że nasza definicja faktycznie terminuje.

Fixpoint f {A : Type} (l : list A) : List A :=
match l with
| [] => In NilR
| h :: t => In (ConsR h (f t))
end.

Możemy łatwo przekształcić starą dobrą listę typu list A (który jest "zwykłym" typem induktywnym) w nową listę typu List A (tego zdefiniowanego za pomocą Mu.

Unset Guard Checking.
Fixpoint g {A : Type} (l : List A) {struct l} : list A :=
match l with
| In NilR => []
| In (ConsR h t) => h :: g t
end.
Set Guard Checking.

Ale żeby przekształcić listę typu List A w listę typu list A, musimy znowu wyłączyć termination checker (albo użyć reguły indukcji, której zdefiniowane również wymagało wyłączenia go).

Lemma fg :
  forall {A : Type} (l : list A),
    g (f l) = l.
Proof.
  induction l as [| h t]; cbn;
  rewrite ?IHt; reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (l : List A),
    f (g l) = l.
Proof.
  intros.
  induction l using List_ind'; cbn.
    reflexivity.
    rewrite IHl. reflexivity.
Qed.

Udowodnienie, że typy list A i List A są izomorficzne jest bardzo łatwe, ale cały proces jest mocno dołujący, gdyż wymaga oszukania najpierw positivity checkera, a potem termination checkera

End List.

Niestety, to nie drobne oszustwa są naszym największym problemem (kto nigdy nie nakłamał babci, że wszystko jest ok?). Problem jest znacznie bardziej poważny: Mu oraz Nu umożliwiają nam udowodnienie fałszu.
Jest tak dlatego, że typy induktywne są punktami stałymi ściśle pozytywnych funktorów wielomianowych, podczas gdy do Mu możemy wstawić dowolny funktor. Podobnie sprawa ma się z Nu.
Zobaczmy, jak to wygląda w praktyce.

Inductive WutR (X : Type) : Type :=
| wutR : (X -> bool) -> WutR X.

Arguments wutR {X} _.

Definition Wut : Type := Mu WutR.

Definition wut (f : Wut -> bool) : Wut :=
  In (wutR f).

Widzieliśmy już w rozdziale o typach induktywnych, że negatywne typy induktywne prowadzą do sprzeczności. Właśnie tego faktu użyjemy by pokazać, że Mu również prowadzi do sprzeczności.
Przykładem negatywnego typu induktywnego jest typ Wut, który spełnia równanie Wut = (Wut -> bool). Mając Mu, możemy zdefiniować Wut] jako najmniejszy punkt stały funktora F(X) = (X -> bool) (który nazwaliśmy wutR).

Definition tuw (w : Wut) : Wut -> bool :=
match w with
| In (wutR f) => f
end.

Będziemy chcieli skorzystać z twierdzenia Cantora, więc potrzebna nam będzie funkcja typu Wut -> Wut -> bool. Zdefiniowanie takiej funkcji jest proste - każde w : Wut ma w środku funkcję Wut -> bool.


Lemma surjective_tuw :
  surjective tuw.
Proof.
  unfold Wut, surjective.
  intro f. exists (In (wutR f)).
  cbn. reflexivity.
Qed.

Funkcja tuw jest surjekcją - ponieważ każdą funkcję Wut -> bool można za pomocą konstruktora wut "włożyć" do typu Wut, to każdą taką funkcję można również wyjąć z jakiegoś elementu typu Wut.

Lemma not_surjective_tuw :
  ~ surjective tuw.
Proof.
  apply (Cantor' tuw negb).
  destruct b; inversion 1.
Qed.

Ale twierdzenie Cantora mówi nam, że tuw nie może być surjekcją - typ funkcji z Wut w bool jest "większy" niż typ Wut.

Lemma Mu_illegal : False.
Proof.
  apply not_surjective_tuw.
  apply surjective_tuw.
Qed.

Ponieważ tuw jednocześnie jest i nie jest surjekcją, dostajemy sprzeczność i jest pozamiatane - typ Mu jest nielegalny.

Definition CoWut : Type := Nu WutR.

Definition tuw' (x y : CoWut) : bool :=
match Out x with
| wutR f => f y
end.

Lemma surjective_tuw' :
  surjective tuw'.
Proof.
  unfold surjective.
  intro f. exists {| Out := wutR f |}.
  unfold tuw'. cbn. reflexivity.
Qed.

Lemma not_surjective_tuw' :
  ~ surjective tuw'.
Proof.
  apply (Cantor' tuw' negb).
  destruct b; inversion 1.
Qed.

Lemma Nu_illegal : False.
Proof.
  apply not_surjective_tuw'.
  apply surjective_tuw'.
Qed.

Sprawa z Nu ma się zupełnie dualnie. Definiujemy typ CoWut, który jest dualny do Wut, tzn. jest największym punktem stałym funktora WutR. Mając element w : CoWut możemy z niego wyjąć dowolną funkcję CoWut -> bool, a zatem mamy surjekcję CoWut -> CoWut -> bool. Z twierdzenia Cantora wiemy jednak, że jest to niemożliwe, a zatem mamy sprzeczność.
Skoro wiemy już, że Mu i Nu nie są dobrym pomysłem, spróbujmy za Tym razem bez oszukiwania

Module List'.

Inductive List (A : Type) : Type :=
| In : ListR A (List A) -> List A.

Arguments In {A} _.

Dużo lepiej jest "zawiązać węzeł" (w ten sposób często nazywane bywają techniki oparte o punkty stałe) ręcznie, bez używania Mu ani Nu, dzięki czemu nie musimy oszukiwać i wyłączać positivity/termination checkera. Żeby to zrobić, wystarczy zdefiniować typ wyglądający jak Mu/Nu, ale za to z podstawionym za F konkretnym funktorem.
To właśnie robimy w powyższej, alternatywnej definicji typu List A: mamy jeden konstruktor In, jak w Mu, ale zamiast F (Mu F) bierze on dużo bardziej konkretny argument o typie ListR A (List A). Dzięki temu Coq akceptuje powyższy typ jako poprawny.

Lemma List_ind' :
  forall
    {A : Type} (P : List A -> Prop)
    (HNil : P (In NilR))
    (HCons : forall (h : A) (t : List A), P t -> P (In (ConsR h t)))
    (l : List A), P l.
Proof.
  fix IH 5.
  destruct l as [[| h t]].
    exact HNil.
    apply HCons, IH; assumption.
Defined.

Teraz regułę indukcji możemy udowodnić bez większych problemów oraz bez oszukiwania.

Fixpoint f {A : Type} (l : list A) : List A :=
match l with
| [] => In NilR
| h :: t => In (ConsR h (f t))
end.

Fixpoint g {A : Type} (l : List A) : list A :=
match l with
| In NilR => []
| In (ConsR h t) => h :: g t
end.

Także funkcja zamieniająca nową listę w starą nie wymaga żadnych oszustw - możemy teraz normalnie programować z wykorzystaniem dopasowań i rekursji, nie musząc się martwić o termination checker.

Lemma fg :
  forall {A : Type} (l : list A),
    g (f l) = l.
Proof.
  induction l as [| h t]; cbn;
  rewrite ?IHt; reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (l : List A),
    f (g l) = l.
Proof.
  intros.
  induction l using List_ind'; cbn.
    reflexivity.
    rewrite IHl. reflexivity.
Qed.

Dowody pozostają równie proste jak poprzednio.

End List'.

Funktory bazowe często używanych typów

Zwykłe typy induktywne

Z wiedzą, którą dotychczas zdobyliśmy, nie trudno wyobrazić sobie, jak wygląda funktor bazowy typu nat.

Module NatF.

Inductive NatF (X : Type) : Type :=
| Zero : NatF X
| Succ : X -> NatF X.

Arguments Zero {X}.
Arguments Succ {X} _.

Inductive Nat : Type :=
| In : NatF Nat -> Nat.

Fixpoint f (n : nat) : Nat :=
match n with
| 0 => In Zero
| Datatypes.S n' => In (Succ (f n'))
end.

Fixpoint g (n : Nat) : nat :=
match n with
| In Zero => 0
| In (Succ n') => Datatypes.S (g n')
end.

Lemma fg :
  forall n : nat,
    g (f n) = n.
Proof.
  induction n as [| n']; cbn.
    reflexivity.
    f_equal. assumption.
Qed.

Lemma gf :
  forall n : Nat,
    f (g n) = n.
Proof.
  fix IH 1.
  destruct n as [[| n']]; cbn.
    reflexivity.
    do 2 f_equal. apply IH.
Qed.

End NatF.

Typy sparametryzowane

Widzieliśmy już funktor bazowy dla list, rzućmy więc jeszcze szybko okiem na funkctor bazowy typu drzew binarnych.

Module BTree.

Inductive BTree (A : Type) : Type :=
| E : BTree A
| N : A -> BTree A -> BTree A -> BTree A.

Arguments E {A}.
Arguments N {A} _ _ _.

Module BTreeR.

Inductive BTreeR (A R : Type) : Type :=
| ER : BTreeR A R
| NR : A -> R -> R -> BTreeR A R.

Arguments ER {A R}.
Arguments NR {A R} _ _ _.

Inductive BTree' (A : Type) : Type :=
| In : BTreeR A (BTree' A) -> BTree' A.

Arguments In {A} _.

Fixpoint f {A : Type} (t : BTree A) : BTree' A :=
match t with
| E => In ER
| N v l r => In (NR v (f l) (f r))
end.

Fixpoint g {A : Type} (t : BTree' A) : BTree A :=
match t with
| In ER => E
| In (NR v l r) => N v (g l) (g r)
end.

Lemma fg :
  forall {A : Type} (t : BTree A), g (f t) = t.
Proof.
  induction t; cbn.
    reflexivity.
    rewrite IHt1, IHt2. reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (t : BTree' A), f (g t) = t.
Proof.
  fix IH 2.
  destruct t as [[]]; cbn.
    reflexivity. rewrite !IH. reflexivity.
Qed.

End BTreeR.

W środowiskach Haskellowych powyższy sposób definiowania funktorów bazowych zowie się "schematami rekursji" (ang. recursion schemes). W Coqu możliwy jest też nieco inny sposób, który zobaczymy poniżej.

Module BTreeF.

Inductive BTreeF (F : Type -> Type) (A : Type) : Type :=
| EF : BTreeF F A
| NF : A -> F A -> F A -> BTreeF F A.

Arguments EF {F A}.
Arguments NF {F A} _ _ _.

Tym razem naszymi parametrami nie są już dwa typy A i R, lecz funktor F : Type -> Type oraz typ A. Przez to poddrzewa reprezentujemy jako F A zamiast R. Poza tymi dwoma drobnymi różnicami, wszystko inne pozostaje niezmienione.

Inductive BTree' (A : Type) : Type :=
| In : BTreeF BTree' A -> BTree' A.

Arguments In {A} _.

Fixpoint f {A : Type} (t : BTree A) : BTree' A :=
match t with
| E => In EF
| N v l r => In (NF v (f l) (f r))
end.

Fixpoint g {A : Type} (t : BTree' A) : BTree A :=
match t with
| In EF => E
| In (NF v l r) => N v (g l) (g r)
end.

Lemma fg :
  forall {A : Type} (t : BTree A), g (f t) = t.
Proof.
  induction t; cbn.
    reflexivity.
    rewrite IHt1, IHt2. reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (t : BTree' A), f (g t) = t.
Proof.
  fix IH 2.
  destruct t as [[]]; cbn.
    reflexivity. rewrite !IH. reflexivity.
Qed.

End BTreeF.

End BTree.

Typy wzajemnie induktywne


Module FinitaryTreeF.

Inductive Tree (A : Type) : Type :=
| Empty : Tree A
| Node : A -> Forest A -> Tree A

with Forest (A : Type) : Type :=
| Nil : Forest A
| Cons : Tree A -> Forest A -> Forest A.

Arguments Empty {A}.
Arguments Node {A} _ _.

Arguments Nil {A}.
Arguments Cons {A} _ _.

Inductive TreeR (A F : Type) : Type :=
| EmptyR : TreeR A F
| NodeR : A -> F -> TreeR A F.

Inductive ForestR (A F T : Type) : Type :=
| NilR : ForestR A F T
| ConsR : T -> F -> ForestR A F T.

Arguments EmptyR {A F}.
Arguments NodeR {A F} _ _.

Arguments NilR {A F T}.
Arguments ConsR {A F T} _ _.

Module TreeR.

Inductive Tree' (A : Type) : Type :=
| InT : TreeR A (Forest' A) -> Tree' A

with Forest' (A : Type) : Type :=
| InF : ForestR A (Forest' A) (Tree' A) -> Forest' A.

Arguments InT {A} _.

Arguments InF {A} _.

Fixpoint f {A : Type} (t : Tree A) : Tree' A :=
match t with
| Empty => InT EmptyR
| Node x ts => InT (NodeR x (fs ts))
end

with fs {A : Type} (ts : Forest A) : Forest' A :=
match ts with
| Nil => InF NilR
| Cons t ts' => InF (ConsR (f t) (fs ts'))
end.

Fixpoint g {A : Type} (t : Tree' A) : Tree A :=
match t with
| InT EmptyR => Empty
| InT (NodeR x ts) => Node x (gs ts)
end

with gs {A : Type} (ts : Forest' A) : Forest A :=
match ts with
| InF NilR => Nil
| InF (ConsR t ts') => Cons (g t) (gs ts')
end.

Lemma fg :
  forall {A : Type} (t : Tree A),
    g (f t) = t

with fsgs :
  forall {A : Type} (ts : Forest A),
    gs (fs ts) = ts.
Proof.
  destruct t as [| x ts]; cbn.
    reflexivity.
    rewrite fsgs. reflexivity.

  destruct ts as [| t ts']; cbn.
    reflexivity.
    rewrite fg, fsgs. reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (t : Tree' A),
    f (g t) = t

with gsfs :
  forall {A : Type} (ts : Forest' A),
    fs (gs ts) = ts.
Proof.
  destruct t as [[| x ts]]; cbn.
    reflexivity.
    rewrite gsfs. reflexivity.

  destruct ts as [[| t ts']]; cbn.
    reflexivity.
    rewrite gf, gsfs. reflexivity.
Qed.

End TreeR.

Module TreeF.

Inductive TreeF (F : Type -> Type) (A : Type) : Type :=
| EmptyF : TreeF F A
| NodeF : A -> F A -> TreeF F A.

Inductive ForestF (T F : Type -> Type) (A : Type) : Type :=
| NilF : ForestF T F A
| ConsF : T A -> F A -> ForestF T F A.

Arguments EmptyF {F A}.
Arguments NodeF {F A} _ _.

Arguments NilF {T F A}.
Arguments ConsF {T F A} _ _.

Inductive Tree' (A : Type) : Type :=
| InT : TreeF Forest' A -> Tree' A

with Forest' (A : Type) : Type :=
| InF : ForestF Tree' Forest' A -> Forest' A.

Arguments InT {A} _.

Arguments InF {A} _.

Fixpoint f {A : Type} (t : Tree A) : Tree' A :=
match t with
| Empty => InT EmptyF
| Node x ts => InT (NodeF x (fs ts))
end

with fs {A : Type} (ts : Forest A) : Forest' A :=
match ts with
| Nil => InF NilF
| Cons t ts' => InF (ConsF (f t) (fs ts'))
end.

Fixpoint g {A : Type} (t : Tree' A) : Tree A :=
match t with
| InT EmptyF => Empty
| InT (NodeF x ts) => Node x (gs ts)
end

with gs {A : Type} (ts : Forest' A) : Forest A :=
match ts with
| InF NilF => Nil
| InF (ConsF t ts') => Cons (g t) (gs ts')
end.

Lemma fg :
  forall {A : Type} (t : Tree A),
    g (f t) = t

with fsgs :
  forall {A : Type} (ts : Forest A),
    gs (fs ts) = ts.
Proof.
  destruct t as [| x ts]; cbn.
    reflexivity.
    rewrite fsgs. reflexivity.

  destruct ts as [| t ts']; cbn.
    reflexivity.
    rewrite fg, fsgs. reflexivity.
Qed.

Lemma gf :
  forall {A : Type} (t : Tree' A),
    f (g t) = t

with gsfs :
  forall {A : Type} (ts : Forest' A),
    fs (gs ts) = ts.
Proof.
  destruct t as [[| x ts]]; cbn.
    reflexivity.
    rewrite gsfs. reflexivity.

  destruct ts as [[| t ts']]; cbn.
    reflexivity.
    rewrite gf, gsfs. reflexivity.
Qed.

End TreeF.

End FinitaryTreeF.

Rodziny indeksowane (TODO)


Require Import List.
Import ListNotations.

From Typonomikon Require Import F3 F4.

Module ListCoList.

Module CoList.

Import F4.

Module CoList_ForallF.

Inductive ForallF
  {A : Type} (R : A -> A -> Prop)
  (F : (A -> A -> Prop) -> CoList A -> CoList A -> Prop)
  : CoList A -> CoList A -> Prop :=
| Nils :
    forall l1 l2 : CoList A,
      uncons l1 = NilF -> uncons l2 = NilF -> ForallF R F l1 l2
| Conss :
    forall (l1 l2 : CoList A) (h1 h2 : A) (t1 t2 : CoList A),
      uncons l1 = ConsF h1 t1 -> uncons l2 = ConsF h2 t2 ->
        R h1 h2 -> F R t1 t2 -> ForallF R F l1 l2.

CoInductive CoForall {A : Type} (R : A -> A -> Prop) (l1 l2 : CoList A) : Prop :=
{
  uncons' : ForallF R CoForall l1 l2
}.

End CoList_ForallF.

End CoList.

End ListCoList.

Rodziny indeksowane za pomocą typów parametryzowanych i równości (TODO)


Module ListCoList2.

Inductive ListR (A R : Type) : Type :=
| NilR : ListR A R
| ConsR : A -> R -> ListR A R.

Arguments NilR {A R}.
Arguments ConsR {A R} _ _.

Inductive List (A : Type) : Type :=
| In : ListR A (List A) -> List A.

Arguments In {A} _.

CoInductive CoList (A : Type) : Type :=
{
  uncons : ListR A (CoList A);
}.

Arguments uncons {A} _.

TODO: Możemy zmniejszyć o połowę ilość pracy do potrzebnej do zdefiniowania wielu użytecznych rodzin typów, po prostu ponownie wykorzystując szablony operujące na "funktorze bazowym", jak poniżej.

Inductive ForallR
  {A R : Type}
  (Unwrap : R -> ListR A R)
  (RA : A -> A -> Prop)
  (RR : R -> R -> Prop)
  (l1 l2 : R)
  : Prop :=
| Nils :
    Unwrap l1 = NilR -> Unwrap l2 = NilR -> ForallR Unwrap RA RR l1 l2
| Conss :
    forall (h1 h2 : A) (t1 t2 : R),
      Unwrap l1 = ConsR h1 t1 -> Unwrap l2 = ConsR h2 t2 ->
        RA h1 h2 -> RR t1 t2 -> ForallR Unwrap RA RR l1 l2.

Definition uncons' {A : Type} (l : List A) : ListR A (List A) :=
match l with
| In x => x
end.

Inductive Forall {A : Type} (R : A -> A -> Prop) (l1 l2 : List A) : Prop :=
| InForall : ForallR uncons' R (Forall R) l1 l2 -> Forall R l1 l2.

CoInductive CoForall {A : Type} (R : A -> A -> Prop) (l1 l2 : CoList A) : Prop :=
{
  uncons'' : ForallR uncons R (CoForall R) l1 l2
}.

Inductive ExistsR
  {A L : Type}
  (Uncons : L -> ListR A L)
  (RA : A -> A -> Prop)
  (RL : L -> L -> Prop)
  (l1 l2 : L)
  : Prop :=
| Here :
    forall (h1 h2 : A) (t1 t2 : L),
      Uncons l1 = ConsR h1 t1 -> Uncons l2 = ConsR h2 t2 ->
        RA h1 h2 -> ExistsR Uncons RA RL l1 l2
| There :
    forall (h1 h2 : A) (t1 t2 : L),
      Uncons l1 = ConsR h1 t1 -> Uncons l2 = ConsR h2 t2 ->
        RL t1 t2 -> ExistsR Uncons RA RL l1 l2.

Inductive Exists {A : Type} (R : A -> A -> Prop) (l1 l2 : List A) : Prop :=
| InExists : ExistsR uncons' R (Exists R) l1 l2 -> Exists R l1 l2.

Inductive CoExists {A : Type} (R : A -> A -> Prop) (l1 l2 : CoList A) : Prop :=
| InCoExists : ExistsR uncons R (CoExists R) l1 l2 -> CoExists R l1 l2.

TODO: Może możemy zdefiniować funkcje generyczne?

Fixpoint cata {A R : Type} (f : ListR A R -> R) (l : List A) : R :=
match l with
| In NilR => f NilR
| In (ConsR h t) => f (ConsR h (cata f t))
end.

CoFixpoint ana {A R : Type} (f : R -> ListR A R) (r : R) : CoList A :=
{|
  uncons :=
    match f r with
    | NilR => NilR
    | ConsR h t => ConsR h (ana f t)
    end
|}.

End ListCoList2.