Sì, è possibile esprimere un tipo preciso per una routine di ordinamento, in modo tale che qualsiasi funzione con quel tipo debba effettivamente ordinare l'elenco di input.
Mentre potrebbe esserci una soluzione più avanzata ed elegante, ne disegnerò solo una elementare.
Useremo una notazione simile a Coq. Iniziamo definendo un predicato che richiede che f: nat -> nat
funge da permutazione su :0 .. n - 1
Definition permutation (n: nat) (f: nat -> nat): Prop :=
(* once restricted, its codomain is 0..n-1 *)
(forall m, m < n -> f m < n) /\
(* it is injective, hence surjective *)
(forall m1 m2, m1 < n -> m2 < n -> f m1 = f m2 -> m1 = m2) .
Un semplice lemma può essere facilmente dimostrato.
Lemma lem1: forall n f, permutation n f -> m < n -> f m < n.
... (* from the def *)
Definiamo qual è il esimo elemento di un elenco avente lunghezza . Questa funzione richiede una dimostrazione che effettivamente detiene.n m < nmnh
m < n
Definition nth {A} {n} (l: list A n) m (h : m < n): A :=
... (* recursion over n *)
Dato un ordine su A
, possiamo esprimere che un elenco è ordinato:
Definition ordering (A: Type) :=
{ leq: A->A->bool |
(* axioms for ordering *)
(forall a, leq a a = true) /\
(forall a b c, leq a b = true -> leq b c = true -> leq a c = true) /\
(forall a b, leq a b = true -> leq b a = true -> a = b)
} .
Definition sorted {A} {n} (o: ordering A) (l: list A n): Prop :=
...
Infine ecco il tipo per un algoritmo di ordinamento:
Definition mysort (A: Type) (o: ordering A) (n: nat) (l: list A n):
{s: list A n | sorted o s /\
exists f (p: permutation n f),
forall (m: nat) (h: m < n),
nth l m h = nth s (f m) (lem1 n f p h) } :=
... (* the sorting algorithm, and a certificate for its output *)
Il tipo di output afferma che l'elenco dei risultati s
è lungo elementi, è ordinato e che esiste una permutazione di che mappa gli elementi nell'elenco di input con quelli dell'elenco di output . Nota che dobbiamo invocare il lemma sopra per dimostrare , che è richiesto da .0 .. n - 1 f ( m ) < nn0 .. n - 1l
s
f( m ) < nnth
Si noti tuttavia che è l'utente, ovvero il programmatore, che deve dimostrare il loro algoritmo di ordinamento corretto. Il compilatore non verificherà semplicemente che l'ordinamento sia corretto: tutto ciò che fa è verificare una prova fornita. In effetti, il compilatore non può fare molto di più: le proprietà semantiche come "questo programma è un algoritmo di ordinamento" sono indecidibili (secondo il teorema di Rice), quindi non possiamo sperare di rendere il passaggio di prova completamente automatico.
In un lontano, lontano futuro, possiamo ancora sperare che i dimostratori di teoremi automatici diventino così intelligenti che gli algoritmi "più" praticamente utilizzati possano essere automaticamente dimostrati corretti. Il teorema di Rice afferma solo che ciò non può essere fatto in tutti i casi. Tutto ciò che possiamo sperare è un sistema corretto, ampiamente applicabile, ma intrinsecamente incompleto.
Come nota finale, a volte si dimentica che anche i sistemi di tipo semplice sono incompleti ! Ad esempio anche in Java
int f(int x) {
if (x+2 != 2+x)
return "Houston, we have a problem!";
return 42;
}
è semanticamente sicuro (restituisce sempre un numero intero), ma il controllo del tipo si lamenterà del ritorno irraggiungibile.