Kirjautuminen

Haku

Tehtävät

Joulukalenteri 2009: Coq

Kirjoittaja: map_ (07.12.2009)

Esittely

Coq on funktionaaliseen ohjelmointiin perustuva todistusapuri. Sen avulla voidaan kirjoittaa formaaleja matemaattisia todistuksia, jotka järjestelmä tarkistaa automaattisesti.

Coq perustuu ohjelmointikielten tyyppiteorian tulokseen nimeltä Curry-Howard -isomorfismi, joka on vastaavuus logiikan ja ohjelmoinnin välillä. Se kertoo, että tietyllä tavalla rakennetun ohjelmointikielen tietotyyppejä voidaan pitää loogisina väitteinä ja näiden tietotyyppien arvoja väitteiden todistuksina.
Esimerkiksi funktio A:lta B:lle, eli A -> B, vastaa loogista väitettä "A:sta seuraa B". Konjunktioa "A ja B" vastaa pari (vrt. kahden alkion struct tai tuple). Disjunktio "A tai B" ilmaistaan tietotyypillä, jonka arvo on joko A tai B -tyyppinen (vrt. C-kielen union).

Väitteet todistetaan keksimällä jokin arvo, jolla on tarvittava tyyppi. Esimerkiksi implikaatio (A ja B) -> (A tai B) todistetaan kirjoittamalla funktio, joka ottaa parametriksi parin ja palauttaa vaikkapa sen ensimmäisen alkion. Toisaalta implikaation (A tai B) -> (A ja B) todistaminen mielivaltaisille A ja B on mahdotonta - sen tyyppistä funktiota ei Coq:ssa voida kirjoittaa.

Edellä kuvatun funktionaalisen ohjelmointikielen päälle on Coq:ssa rakennettu myös interaktiivinen todistustaktiikkakieli. Siinä ei kirjoiteta funktioita suoraan, vaan annetaan käskyjä, kuten "pura konjunktio", "siirrä implikaation etujäsen oletukseksi", "tee induktio x:n suhteen", ...

Esimerkki: Tekstin tulostus

(* Kuten nähdään, Coq ei ole optimoitu Hello Worldin kirjoittamiseen. *)
Require Import String.
Local Open Scope string_scope.
Check "Todistettavasti hauskaa joulua!".

Tulostus:

"Todistettavasti hauskaa joulua!"
     : string

Esimerkki: Fibonaccin luvut

Seuraava ohjelma tulostaa Fibonaccin lukuja:

Require Import List.

Fixpoint fibo (montako : nat) (a : nat) (b : nat) : list nat :=
  match montako with
    | 0 => nil
    | S n => cons a (fibo n b (a + b))
  end.

Eval compute in (fibo 11 1 1).

Tulostus:

     = 1 :: 1 :: 2 :: 3 :: 5 :: 8 :: 13 :: 21 :: 34 :: 55 :: 89 :: nil
     : list nat

Esimerkki: Todistuksia

(* Alussa mainittu esimerkki: *)
Definition a_ja_b__a_tai_b :
  (* Väitetään, että kaikille propositioille A ja B pätee:
     jos A ja B niin A tai B.
   *)
  forall A B : Prop, A /\ B -> A \/ B :=
  (* Todistus on funktio, joka ottaa propositiot A ja B
     sekä A /\ B-tyyppisen arvon (parin).
   *)
  fun A B a_ja_b =>
    match a_ja_b with   (* Puretaan annettu pari *)
      | conj a b =>     (* Nimetään parin alkiot a:ksi ja b:ksi. *)
          @or_introl A B a   (* Rakennetaan disjunktio A \/ B antamalla a *)
    end.


(* Coq:ssa mahdottomuus ilmaistaan tyypillä False. Falsen tyyppisiä
   arvoja ei ole olemassa.

   A:n negaatio "ei A" ilmaistaan tyypillä A -> False, eli
   "A:sta seuraa mahdottomuus".
 *)

(* Osoitetaan, että implikaatio A \/ B -> A /\ B on mahdoton.
   Tämä tehdään tuottamalla funktio, joka
   ottaa virheellisen implikaation A \/ B -> A /\ B
   todistuksen ja rakentaa sen avulla todistuksen mahdottomuudelle.
 *)
Definition ei__a_tai_b__a_ja_b :
  (forall A B : Prop, A \/ B -> A /\ B) -> False :=
  (* Tehdään vastaoletus:
     Oletetaan, että A \/ B -> A /\ B pätee ja
     johdetaan siitä mahdottomuus.
   *)
  fun kummallinen =>
    (* Meillä on siis nyt käytössä funktio "kummallinen",
       joka tekee disjunktiosta konjunktion.
       Osaamme luoda disjunktion True \/ False, missä "True"
       on triviaalisti todistuva väite, joka todistetaan
       yksinkertaisesti termillä "I".
     *)
    let true_tai_false := @or_introl True False I
      (* Nyt voimme antaa disjunktion funktiolle "kummallinen"
         ja saamme ulos konjunktion True /\ False.
         Tämä on jo selkeä ristiriita.
       *)
      in let true_ja_false := kummallinen True False true_tai_false
           (* Nyt riitää enää purkaa konjunktio ja palauttaa sen
              oikea puoli.
            *)
           in match true_ja_false with
                | conj t f => f
              end.


(* Tässä vielä esimerkin vuoksi todistustaktiikoilla kirjoitettu
   hyvin lyhyt todistus sille, että 0 ei ole 1.
 *)
Theorem nolla_ei_ole_yksi : 0 = 1 -> False.
Proof.
  intro vastaoletus. (* Otetaan implikaation etujäsen oletukseksi. *)
  (* Nyt meillä on yksi oletus nimeltä "vastaoletus", jonka tyyppi on
     0 = 1. Todistettavana on enää False. *)

  (* Annetaan Coq:n päätellä, millä tavoilla
     yhtäsuuruus 0 = 1 on voitu rakentaa.
     Coq huomaa helposti, että moisen yhtäsuuruuden rakentaminen
     on mahdotonta, joten todistus on valmis.
   *)
  inversion vastaoletus.
Qed.

Hauska tietää

Coq:ssa on mahdotonta kirjoittaa ikuista silmukkaa.
Ikuinen silmukka vastaisi Coq:ssa kehäpäätelmää, jonka avulla voitaisiin todistaa mitä tahansa.

Linkkejä

Tietoa sivustosta