Probar una operación de clasificación en sistema de tipos

9

Quiero saber hasta qué punto un sistema de tipos en un lenguaje de programación puede ser beneficioso. Por ejemplo, sé que en un lenguaje de programación de tipo dependiente, podemos crear una Vectorclase que incorpore el tamaño del vector en la firma de tipo. Es como un ejemplo de facto. También podemos escribir una función appendusando esas firmas para que el compilador demuestre que el tamaño de la lista resultante será la suma de las listas de entrada.

¿Hay alguna forma de codificar, por ejemplo, en la firma de tipo de un algoritmo de clasificación para que el compilador garantice que la lista resultante sea una permutación de la lista de entrada? ¿Cómo se puede hacer esto, si es posible?

meguli
fuente

Respuestas:

13

Sí, es posible expresar un tipo preciso para una rutina de clasificación, de modo que cualquier función que tenga ese tipo debe ordenar la lista de entrada.

Si bien puede haber una solución más avanzada y elegante, esbozaré una solución primaria, solo.

f: nat -> nat0..n1

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 simple lema puede ser fácilmente probado.

Lemma lem1: forall n f, permutation n f -> m < n -> f m < n.
... (* from the def *)

mnhm<n

Definition nth {A} {n} (l: list A n) m (h : m < n): A :=
... (* recursion over n *)

Dado un pedido A, podemos expresar que una lista está ordenada:

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 :=
...

Finalmente, aquí está el tipo para un algoritmo de clasificación:

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 *)

sn0..n1lsf(m)<nnth

Sin embargo, tenga en cuenta que es el usuario, es decir, el programador, el que tiene que demostrar que su algoritmo de clasificación es correcto. El compilador no solo verificará que la clasificación sea correcta: todo lo que hace es verificar una prueba provista. De hecho, el compilador no puede hacer mucho más que eso: las propiedades semánticas como "este programa es un algoritmo de clasificación" son indecidibles (por el teorema de Rice), por lo que no podemos esperar que el paso de prueba sea completamente automático.

En el futuro lejano, aún podemos esperar que los probadores de teoremas automáticos se vuelvan tan inteligentes que "la mayoría" de los algoritmos utilizados en la práctica puedan probarse automáticamente como correctos. El teorema de Rice solo establece que esto no se puede hacer en todos los casos. Todo lo que podemos esperar es un sistema correcto, ampliamente aplicable, pero inherentemente incompleto.

Como nota final, a veces se olvida que incluso los sistemas de tipos simples están incompletos . Por ejemplo, incluso en Java

int f(int x) {
   if (x+2 != 2+x)
      return "Houston, we have a problem!";
   return 42;
}

es semánticamente seguro de tipo (siempre devuelve un número entero), pero el verificador de tipo se quejará del retorno inalcanzable.

chi
fuente