D3: Logika boolowska, rozstrzygalność i reflekcja [TODO]
 
 UWAGA: ten rozdział właśnie ulega konceptualnemu przeobrażeinu i
    może być nie na miejscu, tzn. zawierać rzeczy, o których jeszcze
    nie było mowy. 
 
Logika boolowska
 
 Zadania z funkcji boolowskich, sprawdzające radzenie sobie w pracy
    z enumeracjami, definiowaniem funkcji przez dopasowanie do wzorca
    i dowodzeniem przez rozbicie na przypadki.
 
    Chciałem, żeby nazwy twierdzeń odpowiadały tym z biblioteki standardowej,
    ale na razie nie mam siły tego ujednolicić. 
 
Section boolean_functions.
Variables b b1 b2 b3 : bool.
Definicje
 
 Zdefiniuj następujące funkcje boolowskie:
-  negb (negacja)
 
-  andb (koniunkcja)
 
-  orb (alternatywa)
 
-  implb (implikacja)
 
-  eqb (równoważność)
 
-  xorb (alternatywa wykluczająca)
 
-  nor
 
-  nand 
 
 
Notation "b1 && b2" := (andb b1 b2).
Notation "b1 || b2" := (orb b1 b2).
Twierdzenia
 
 Udowodnij, że zdefiniowane przez ciebie funkcje mają spodziewane
    właściwości. 
 
Lemma negb_inv : negb (negb b) = b.
Lemma negb_ineq : negb b <> b.
Lemma andb_elim_l : b1 && b2 = true -> b1 = true.
Lemma andb_elim_r : b1 && b2 = true -> b2 = true.
Lemma andb_elim : b1 && b2 = true -> b1 = true /\ b2 = true.
Lemma orb_elim : b1 || b2 = true -> b1 = true \/ b2 = true.
Lemma andb_true_neutral_l : true && b = b.
Lemma andb_true_neutral_r : b && true = b.
Lemma orb_false_neutral_l : false || b = b.
Lemma orb_false_neutral_r : b || false = b.
Lemma andb_false_annihilation_l : false && b = false.
Lemma andb_false_annihilation_r : b && false = false.
Lemma orb_true_annihilation_l :  true || b = true.
Lemma orb_true_annihilation_r :  b || true = true.
Lemma andb_assoc : b1 && (b2 && b3) = (b1 && b2) && b3.
Lemma orb_assoc : b1 || (b2 || b3) = (b1 || b2) || b3.
Lemma andb_comm : b1 && b2 = b2 && b1.
Lemma orb_comm : b1 || b2 = b2 || b1.
Lemma andb_dist_orb :
  b1 && (b2 || b3) = (b1 && b2) || (b1 && b3).
Lemma orb_dist_andb :
  b1 || (b2 && b3) = (b1 || b2) && (b1 || b3).
Wyłączony środek i niesprzeczność
 
Lemma andb_negb : b && negb b = false.
Lemma orb_negb : b || negb b = true.
Lemma negb_andb : negb (b1 && b2) = negb b1 || negb b2.
Lemma negb_orb : negb (b1 || b2) = negb b1 && negb b2.
Lemma eqb_spec : eqb b1 b2 = true -> b1 = b2.
Lemma eqb_spec' : eqb b1 b2 = false -> b1 <> b2.
Lemma xorb_spec :
  xorb b1 b2 = negb (eqb b1 b2).
Lemma xorb_spec' :
  xorb b1 b2 = true -> b1 <> b2.
Lemma norb_spec :
  norb b1 b2 = negb (b1 || b2).
Lemma nandb_spec :
  nandb b1 b2 = negb (b1 && b2).
Lemma andb_eq_orb :
  b1 && b2 = b1 || b2 -> b1 = b2.
Lemma all3_spec :
  (b1 && b2) || (negb b1 || negb b2) = true.
Lemma noncontradiction_bool :
  negb (eqb b (negb b)) = true.
Lemma excluded_middle_bool :
  b || negb b = true.
End boolean_functions.
Rekordy (TODO)
 
 W wielu językach programowania występują typy rekordów (ang. record
    types). Charakteryzują się one tym, że mają z góry określoną ilość
    pól o potencjalnie różnych typach. W językach imperatywnych rekordy
    wyewoluowały zaś w obiekty, które różnią się od rekordów tym, że mogą
    zawierać również funkcje, których dziedziną jest obiekt, w którym
    funkcja się znajduje.
 
    W Coqu mamy do dyspozycji rekordy, ale nie obiekty. Trzeba tu po raz
    kolejny pochwalić siłę systemu typów Coqa — o ile w większości języków
    rekordy są osobnym konstruktem językowym, o tyle w Coqu mogą być one z
    łatwością reprezentowane przez typy induktywne z jednym konstruktorem
    (wraz z odpowiednimi projekcjami, które dekonstruują rekord). 
 
Module rational2.
Record rational : Set :=
{
    sign : bool;
    numerator : nat;
    denominator : nat;
    denominator_not_zero : denominator <> 0
}.
Z typem induktywnym o jednym konstruktorze już się zetknęliśmy,
    próbując zdefiniować liczby wymierne. Powyższa definicja używająca
    rekordu ma drobną przewagę nad poprzednią, w której słowo kluczowe
    
Inductive pada explicité:
-  wygląda ładniej
 
-  ma projekcje 
 
 
Check sign.
Check denominator_not_zero.
Dzięki projekcjom mamy dostęp do poszczególnych pól rekordu bez
    konieczności jego dekonstruowania — nie musimy używać konstruktu
    
match ani taktyki 
destruct, jeżeli nie chcemy. Często bywa to
    bardzo wygodne.
 
    Projekcję 
sign możemy interpretować jako funkcję, która bierze
    liczbę wymierną 
r i zwraca jej znak, zaś projekcja
    
denominator_not_zero mówi nam, że mianownik żadnej liczb wymiernej
    nie jest zerem.
 
    Pozwa tymi wizualno-praktycznymi drobnostkami, obie definicje są
    równoważne (w szczególności, powyższa definicja, podobnie jak
    poprzednia, nie jest dobrą reprezentacją liczb wymiernych). 
 
End rational2.
Ćwiczenie (kalendarz)
 
 Zdefiniuj typ induktywny reprezentujący datę i napisz ręcznie
    wszystkie projekcje. Następnie zdefiniuj rekord reprezentujący
    datę i zachwyć się tym, ile czasu i głupiego pisania zaoszczędziłbyś,
    gdybyś od razu użył rekordu... 
 
Klasy (TODO)
 
 Mechanizmem ułatwiającym życie jeszcze bardziej niż rekordy są klasy.
    Niech nie zmyli cię ta nazwa — nie mają one nic wspólnego z klasami
    znanymi z języków imperatywnych. Bliżej im raczej do interfejsów,
    od których są zresztą dużo silniejsze.
 
    W językach imperatywnych interfejs możemy zaimplementować zazwyczaj
    definiując nowy typ. W Coqu możemy uczynić typ instancją klasy w
    dowolnym miejscu — nawet jeżeli to nie my go zdefiniowaliśmy. Co
    więcej, instancjami klas mogą być nie tylko typy, ale dowolne termy.
    Klasy są w Coqu pełnoprawnym tworem — mogą mieć argumenty, zawierać
    inne klasy, być przekazywane jako argumenty do funkcji etc. Używa się
    ich zazwyczaj dwojako:
-  zamiast rekordów (zwiększa to nieco czytelność)
 
-  jako interfejsy 
 
 
Class EqDec (A : Type) : Type :=
{
    eq_dec : A -> A -> bool;
    eq_dec_spec : forall x y : A, eq_dec x y = true <-> x = y
}.
Nie będziemy po raz trzeci powtarzać (kulawej) definicji liczb
    wymiernych — użycie do tego klas zamiast rekordów sprowadza się
    do zamienienia słowa kluczowego 
Record na 
Class w poprzedniej
    definicji.
 
    Przyjrzyjmmy się za to wykorzystaniu klasy w roli interfejsu.
    Argument 
A : Type po nazwie klasy mówi nam, że nasz interfejs
    będą mogły implementować typy. Dalej zapis 
: Type mówi nam, że
    nasza klasa jest typem — klasy, jako ulepszone rekordy, są typami
    induktywnymi z jednym konstruktorem.
 
    Nasza klasa ma dwa pola, które będzie musiał podać użytkownik chcący
    uczynić swój typ jej instancją: funkcję 
eq_dec oraz jej specyfikację,
    która mówi nam, że 
eq_dec zwraca 
true wtedy i tylko wtedy, gdy jej
    argumenty są równe.
 
    Wobec tego typy będące instancjami 
EqDec można interpretować jako
    typy, dla których równość elementów można sprawdzić za pomocą jakiegoś
    algorytmu. Nie wszystkie typy posiadają tę własność — problematyczne
    są szczególnie te, których elementy są w jakiś sposób "nieskończone". 
 
#[refine]
Instance EqDec_bool : EqDec bool :=
{
    eq_dec := fun b b' : bool =>
      match b, b' with
        | true, true => true
        | false, false => true
        | _, _ => false
      end
}.
Proof.
  destruct x, y; split; trivial; inversion 1.
Defined.
Instancje klas definiujemy przy pomocy słowa kluczowego 
#[refine]
Instance.
    Jeżeli używamy klasy jako interfejsu, który implementować mogą typy,
    to zazwyczaj będziemy potrzebować tylko jednej instancji, więc jej
    nazwa będzie niemal identyczna jak jej typ (dzięki temu łatwo będzie
    ją zapamiętać).
 
    Po symbolu 
:= w nawiasach klamrowych definiujemy pola, które
    nie są dowodami. Całość, jako komenda, musi kończyć się kropką. Gdy
    klasa nie zawiera żadnych pól będących dowodami, definicja jest
    zakończona. W przeciwnym przypadku Coq przechodzi w tryb dowodzenia,
    w którym każdemu polu będącemu dowodem odpowiada jeden podcel. Po
    rozwiązaniu wszystkich podcelów instancja jest zdefiniowana.
 
    W naszym przypadku klasa ma dwa pola — funkcję i dowód na to, że
    funkcja spełnia specyfikację — więc w nawiasach klamrowych musimy
    podać jedynie funkcję. Zauważmy, że nie musimy koniecznie definiować
    jej właśnie w tym miejscu — możemy zrobić to wcześniej, np. za pomocą
    komendy 
Definition albo 
Fixpoint, a tutaj odnieść się do niej
    używając jej nazwy. W przypadku bardziej skomplikowanych definicji
    jest to nawet lepsze wyjście, gdyż zyskujemy dzięki niemu kontrolę
    nad tym, w którym miejscu rozwinąć definicję, dzięki czemu kontekst
    i cel stają się czytelniejsze.
 
    Ponieważ nasza klasa ma pole, które jest dowodem, Coq przechodzi w tryb
    dowodzenia. Dowód, mimo iż wymaga rozpatrzenia ośmiu przypadków, mieści
    się w jednej linijce — widać tutaj moc automatyzacji. Prześledźmy, co
    się w nim dzieje.
 
    Najpierw rozbijamy wartości boolowskie 
x i 
y. Nie musimy wcześniej
    wprowadzać ich do kontekstu taktyką 
intros, gdyż 
destruct sam potrafi
    to zrobić. W wyniku tego dostajemy cztere podcele. W każdym z nich
    taktyką 
split rozbijamy równoważność na dwie implikacje. Sześć z nich
    ma postać 
P -> P, więc radzi sobie z nimi taktyka 
trivial. Dwie
    pozostałe mają przesłanki postaci 
false = true albo 
true = false,
    które są sprzeczne na mocy omówionych wcześniej właściwości konstruktorów.
    Taktyką 
inversion 1 wskazujemy, że pierwsza przesłanka implikacji
    zawiera taką właśnie sprzeczną równość termów zrobionych różnymi
    konstruktorami, a Coq załatwia za nas resztę.
 
    Jeżeli masz problem z odczytaniem tego dowodu, koniecznie przeczytaj
    ponownie fragment rozdziału pierwszego dotyczący kombinatorów taktyk.
    Jeżeli nie potrafisz wyobrazić sobie podcelów generowanych przez
    kolejne taktyki, zastąp chwilowo średniki kropkami, a jeżeli to nie
    pomaga, udowodnij całe twierdzenie bez automatyzacji.
 
    Dzięki takim ćwiczeniom prędzej czy później oswoisz się z tym sposobem
    dowodzenia, choć nie jest to sztuka prosta — czytanie cudzych dowodów
    jest równie trudne jak czytanie cudzych programów.
 
    Prawie nigdy zresztą nowopowstałe dowody nie są od razu zautomatyzowane
    aż w takim stopniu — najpierw są przeprowadzone w części lub w całości
    ręcznie. Automatyzacja jest wynikiem dostrzeżenia w dowodzie pewnych
    powtarzających się wzorców. Proces ten przypomina trochę
    refaktoryzację kodu — gdy dostrzeżemy powtarzające się fragmenty kodu,
    przenosimy je do osobnych procedur. Analogicznie, gdy dostrzegamy
    powtarzające się fragmenty dowodu, łączymy je kombinatorami taktyk
    lub piszemy własne, zupełnie nowe taktyki (temat pisania własnych
    taktyk poruszę prędzej czy później).
 
    Od teraz będę zakładał, że nie masz problemów ze zrozumieniem takich
    dowodów i kolejne przykładowe dowody będę pisał w bardziej zwratej
    formie.
 
    Zauważ, że definicję instancji kończymy komendą 
Defined, a nie 
Qed,
    jak to było w przypadku dowodów twierdzeń. Wynika to z faktu, że Coq
    inaczej traktuje specyfikacje i programy, a inaczej zdania i dowody.
    W przypadku dowodu liczy się sam fakt jego istnienia, a nie jego treść,
    więc komenda 
Qed każe Coqowi zapamiętać jedynie, że twierdzenie
    udowodniono, a zapomnieć, jak dokładnie wyglądał proofterm. W przypadku
    programów takie zachowanie jest niedopuszczalne, więc 
Defined każe
    Coqowi zapamiętać term ze wszystkimi szczegółami. Jeżeli nie wiesz,
    której z tych dwóch komend użyć, użyj 
Defined. 
 
Ćwiczenie (EqDec)
 
 Zdefiniuj instancje klasy 
EqDec dla typów 
unit oraz 
nat. 
 
Ćwiczenie (równość funkcji)
 
 Czy możliwe jest zdefiniowanie instancji klasy 
EqDec dla typu:
-  bool -> bool
 
-  bool -> nat
 
-  nat -> bool
 
-  nat -> nat
 
-  Prop 
 
 
 Jeżeli tak, udowodnij w Coqu. Jeżeli nie, zaargumentuj słownie. 
 
#[refine]
Instance EqDec_option (A : Type) (_ : EqDec A) : EqDec (option A) :=
{
    eq_dec := fun opt1 opt2 : option A =>
      match opt1, opt2 with
        | Some a, Some a' => eq_dec a a'
        | None, None => true
        | _, _ => false
      end
}.
Proof.
  destruct x, y; split; trivial; try (inversion 1; fail); intro.
    apply (eq_dec_spec a a0) in H. subst. trivial.
    apply (eq_dec_spec a a0). inversion H. trivial.
Defined.
Instancje klas mogą przyjmować argumenty, w tym również instancje innych
    klas albo inne instancje tej samej klasy. Dzięki temu możemy wyrazić
    ideę interfejsów warunkowych.
 
    W naszym przypadku typ 
option A może być instancją klasy 
EqDec
    jedynie pod warunkiem, że jego argument również jest instancją tej
    klasy. Jest to konieczne, gdyż porównywanie termów typu 
option A
    sprowadza się do porównywania termów typu 
A.
 
    Zauważ, że kod 
eq_dec a a' nie odwołuje się do definiowanej właśnie
    funkcji 
eq_dec dla typu 
option A — odnosi się do funkcji 
eq_dec,
    której dostarcza nam instancja 
_ : EqDec A. Jak widać, nie musimy
    nawet nadawać jej nazwy — Coqa interesuje tylko jej obecność.
 
    Na podstawie typów termów 
a i 
a', które są Coqowi znane, potrafi
    on wywnioskować, że 
eq_dec a a' nie jest wywołaniem rekurencyjnym,
    lecz odnosi się do instancji innej niż obecnie definiowana. Coq może
    ją znaleźć i odnosić się do niej, mimo że my nie możemy (gdybyśmy
    chcieli odnosić się do tej instancji, musielibyśmy zmienić nazwę z
    
_ na coś innego). 
 
Ćwiczenie (równość list)
 
 Zdefiniuj instancję klasy 
EqDec dla typu 
list A. 
 
Ćwiczenie (równość funkcji 2)
 
 Niech 
A i 
B będą dowolnymi typami. Zastanów się, kiedy możliwe
    jest zdefiniowanie instancji klasy 
EqDec dla 
A -> B. 
 
Rozstrzygalność
 
Lemma excluded_middle :
  forall P : Prop, P \/ ~ P.
Proof.
  intro. left.
Restart.
  intro. right. intro.
Abort.
Próba udowodnienia tego twierdzenia pokazuje nam zasadniczą różnicę
    między logiką konstruktywną, która jest domyślną logiką Coqa, oraz
    logiką klasyczną, najpowszechniej znanym i używanym rodzajem logiki.
 
    Każde zdanie jest, w pewnym "filozoficznym" sensie, prawdziwe lub
    fałszywe i to właśnie powyższe zdanie oznacza w logice klasycznej.
    Logika konstruktywna jednak, jak już wiemy, nie jest logiką prawdy,
    lecz logiką udowadnialności i ma swoją interpretację obliczeniową.
    Powyższe zdanie w logice konstruktywnej oznacza: program komputerowy
    
exluded_middle rozstrzyga, czy dowolne zdanie jest prawdziwe, czy
    fałszywe.
 
    Skonstruowanie programu o takim typie jest w ogólności niemożliwe,
    gdyż dysponujemy zbyt małą ilością informacji: nie wiemy czym jest
    zdanie 
P, a nie posiadamy żadnego ogólnego sposobu dowodzenia lub
    obalania zdań o nieznanej nam postaci. Nie możemy np. użyć indukcji,
    gdyż nie wiemy, czy zdanie 
P zostało zdefiniowane induktywnie, czy
    też nie. W Coqu jedynym sposobem uzyskania termu o typie 
forall
    P : Prop, P \/ ~ P jest przyjęcie go jako aksjomat. 
 
Lemma True_dec : True \/ ~ True.
Proof.
  left. trivial.
Qed.
Powyższe dywagacje nie przeszkadzają nam jednak w udowadnianiu,
    że reguła wyłączonego środka zachodzi dla pewnych konkretnych
    zdań. Zdanie takie będziemy nazywać zdaniami rozstrzygalnymi
    (ang. decidable). O pozostałych zdaniach będziemy mówić, że są 
    nierozstrzygalne (ang. undecidable). Ponieważ w Coqu wszystkie
    funkcje są rekurencyjne, a dowody to programy, to możemy powyższą
    definicję rozumieć tak: zdanie jest rozstrzygalne, jeżeli istnieje
    funkcja rekurencyjna o przeciwdzidzinie 
bool, która sprawdza, czy
    jest ono prawdziwe, czy fałszywe.
 
    Przykładami zdań, predykatów czy problemów rozstrzygalnych są:
-  sprawdzanie, czy lista jest niepusta
 
-  sprawdzanie, czy liczba naturalna jest parzysta
 
-  sprawdzanie, czy dwie liczby naturalne są równe 
 
 
 Przykładem problemów nierozstrzygalnych są:
-  dla funkcji f g : nat -> nat sprawdzenie, czy
      forall n : nat, f n = g n — jest to w ogólności niemożliwe,
      gdyż wymaga wykonania nieskończonej ilości porównań (co nie
      znaczy, że nie da się rozwiązać tego problemu dla niektórych
      funkcji)
 
-  sprawdzenie, czy słowo o nieskończonej długości jest palindromem 
 
 
Ćwiczenie
 
Lemma eq_nat_dec :
  forall n m : nat, n = m \/ ~ n = m.
Techniczne apekty rozstrzygalności
 
 Podsumowując powyższe rozważania, moglibyśmy stwierdzić: zdanie 
P jest
    rozstrzygalne, jeżeli istnieje term typu 
P \/ ~ P. Stwierdzenie takie
    nie zamyka jednak sprawy, gdyż bywa czasem mocno bezużyteczne.
 
    Żeby to zobrazować, spróbujmy użyć twierdzenia 
eq_nat_dec do napisania
    funkcji, która sprawdza, czy liczna naturalna 
n występuje na liście
    liczb naturalnych 
l: 
 
Fail Fixpoint inb_nat (n : nat) (l : list nat) : bool :=
match l with
    | nil => false
    | cons h t =>
        match eq_nat_dec n h with
            | or_introl _ => true
            | _ => inb_nat n t
        end
end.
Coq nie akceptuje powyższego kodu, racząc nas informacją o błędzie: 
Nasza porażka wynika z faktu, że do zdefiniowania funkcji, która
    jest programem (jej dziedzina i przeciwdziedzina są sortu 
Type)
    próbowaliśmy użyć termu 
eq_nat_dec n h, który jest dowodem
    (konkluzją 
eq_nat_dec jest równość, która jest sortu 
Prop).
 
    Mimo korespondencji Curry'ego-Howarda, która odpowiada za olbrzymie
    podobieństwo specyfikacji i zdań, programów i dowodów, sortu 
Type
    i sortu 
Prop, są one rozróżniane i niesie to za sobą konsekwencje:
    podczas gdy programów możemy używać wszędzie, dowodów możemy używać
    jedynie do konstruowania innych dowodów.
 
    Praktycznie oznacza to, że mimo iż równość liczb naturalnych jest
    rozstrzygalna, pisząc program nie mamy możliwości jej rozstrzygania
    za pomocą 
eq_nat_dec. To właśnie miałem na myśli pisząc, że termy
    typu 
P \/ ~ P są mocno bezużyteczne.
 
    Uszy do góry: nie wszystko stracone! Jest to tylko drobna przeszkoda,
    którą bardzo łatwo ominąć: 
 
Inductive sumbool (A B : Prop) : Type :=
    | left : A -> sumbool A B
    | right : B -> sumbool A B.
Typ 
sumbool jest niemal dokładną kopią 
or, jednak nie żyje on
    w 
Prop, lecz w 
Type. Ta drobna sztuczka, że termy typu
    
sumbool A B formalnie są programami, mimo że ich naturalna
    interpretacja jest taka sama jak 
or, a więc jako dowodu
    dysjunkcji. 
 
Ćwiczenie
 
 Udowodnij twierdzenie 
eq_nat_dec' o rozstrzygalności 
= na
    liczbach naturalnych. Użyj typu 
sumbool. Następnie napisz
    funkcję 
inb_nat, która sprawdza, czy liczba naturalna 
n
    jest obecna na liście 
l. 
 
Kiedy nie warto rozstrzygać? (TODO)
 
 Tutaj coś w stylu 
n < m \/ n = m \/ n > m 
 
Rozstrzygalność jako pułapka na negacjonistów (TODO)
 
Techniczne aspekty rozstrzygalności 2 (TODO)
 
Reflekcja w małej skali (TODO)