La lógica de Hoare (también conocida como lógica de Floyd-Hoare o reglas de Hoare ) es un sistema formal con un conjunto de reglas lógicas para razonar rigurosamente sobre la corrección de los programas de computadora . Fue propuesto en 1969 por el informático y lógico británico Tony Hoare , y posteriormente perfeccionado por Hoare y otros investigadores. [1] Las ideas originales fueron sembradas por el trabajo de Robert W. Floyd , quien había publicado un sistema similar [2] para diagramas de flujo .
La característica central de la lógica de Hoare es la tripleta de Hoare . Un triple describe cómo la ejecución de un fragmento de código cambia el estado del cálculo. Un triple de Hoare tiene la forma
donde y son afirmaciones y es un comando . [nota 1] se denomina condición previa y poscondición : cuando se cumple la condición previa, la ejecución del comando establece la poscondición . Las afirmaciones son fórmulas en lógica de predicados .
La lógica de Hoare proporciona axiomas y reglas de inferencia para todas las construcciones de un lenguaje de programación imperativo simple . Además de las reglas para el lenguaje simple en el artículo original de Hoare, desde entonces Hoare y muchos otros investigadores han desarrollado reglas para otras construcciones del lenguaje. Hay reglas para concurrencia , procedimientos , saltos y punteros .
Utilizando la lógica estándar de Hoare, sólo se puede demostrar una corrección parcial . La corrección total requiere además la terminación , que se puede probar por separado o con una versión ampliada de la regla While. [3] Así, la lectura intuitiva de una tripleta de Hoare es: Siempre que se mantenga el estado antes de la ejecución de , se mantendrá después o no terminará. En el último caso, no hay "después", por lo que puede haber cualquier declaración. En efecto, uno puede optar por ser falso para expresar algo que no termina.
"Terminación" aquí y en el resto de este artículo se entiende en el sentido más amplio de que el cálculo eventualmente terminará, es decir, implica la ausencia de bucles infinitos; no implica la ausencia de violaciones de los límites de implementación (por ejemplo, división por cero) que detengan el programa prematuramente. En su artículo de 1969, Hoare utilizó una noción más estrecha de terminación que también implicaba la ausencia de violaciones de los límites de implementación, y expresó su preferencia por la noción más amplia de terminación, ya que mantiene las afirmaciones independientes de la implementación: [4]
Otra deficiencia de los axiomas y reglas citados anteriormente es que no proporcionan ninguna base para demostrar que un programa termina exitosamente. La imposibilidad de terminar puede deberse a un bucle infinito; o puede deberse a una violación de un límite definido por la implementación, por ejemplo, el rango de operandos numéricos, el tamaño del almacenamiento o un límite de tiempo del sistema operativo. Por lo tanto, la notación " " debe interpretarse "siempre que el programa finalice exitosamente, las propiedades de sus resultados se describen mediante ". Es bastante fácil adaptar los axiomas para que no puedan usarse para predecir los “resultados” de programas que no terminan; pero el uso real de los axiomas dependería ahora del conocimiento de muchas características dependientes de la implementación, por ejemplo, el tamaño y la velocidad de la computadora, el rango de números y la elección de la técnica de desbordamiento. Aparte de las pruebas de cómo evitar bucles infinitos, probablemente sea mejor probar la corrección "condicional" de un programa y confiar en una implementación para dar una advertencia si ha tenido que abandonar la ejecución del programa como resultado de la violación de una límite de implementación.
La regla de la declaración vacía afirma que la declaración de salto no cambia el estado del programa, por lo tanto, todo lo que sea cierto antes del salto también lo será después. [nota 2]
El axioma de asignación establece que, después de la asignación, cualquier predicado que antes era verdadero para el lado derecho de la asignación ahora es válido para la variable. Formalmente, sea P una afirmación en la que la variable x es libre . Entonces:
donde denota la afirmación P en la que cada aparición libre de x ha sido reemplazada por la expresión E .
El esquema del axioma de asignación significa que la verdad de es equivalente a la verdad posterior a la asignación de P. Por lo tanto, si fueran verdaderas antes de la asignación, según el axioma de asignación, entonces P sería verdadera después de la cual. Por el contrario, si fuera falso (es decir, verdadero) antes de la declaración de asignación, P debe ser falso después.
Ejemplos de triples válidos incluyen:
Todas las condiciones previas que no sean modificadas por la expresión se pueden trasladar a la condición posterior. En el primer ejemplo, la asignación no cambia el hecho de que , por lo que ambas declaraciones pueden aparecer en la poscondición. Formalmente, este resultado se obtiene aplicando el esquema del axioma donde P es ( y ), lo que produce ser ( y ), que a su vez puede simplificarse a la condición previa dada .
El esquema del axioma de asignación equivale a decir que para encontrar la condición previa, primero se toma la condición posterior y se reemplazan todas las apariciones del lado izquierdo de la tarea con el lado derecho de la tarea. Tenga cuidado de no intentar hacer esto al revés siguiendo esta forma incorrecta de pensar: ; esta regla lleva a ejemplos sin sentido como:
Otra regla incorrecta que parece tentadora a primera vista es ; conduce a ejemplos sin sentido como:
Si bien una poscondición determinada P determina de forma única la precondición , lo contrario no es cierto. Por ejemplo:
son ejemplos válidos del esquema del axioma de asignación.
El axioma de asignación propuesto por Hoare no se aplica cuando más de un nombre puede referirse al mismo valor almacenado. Por ejemplo,
es incorrecto si x e y se refieren a la misma variable ( aliasing ), aunque es una instancia adecuada del esquema del axioma de asignación (con ambos y siendo ).
La regla de composición de Hoare se aplica a los programas ejecutados secuencialmente S y T , donde S se ejecuta antes de T y se escribe ( Q se llama condición intermedia ): [5]
Por ejemplo, considere los siguientes dos casos del axioma de asignación:
y
Por la regla de secuenciación se concluye:
Otro ejemplo se muestra en el cuadro de la derecha.
La regla condicional establece que una poscondición Q común a la parte then y else también es una poscondición del todo if...endif . [6] En la parte entonces y en la otra parte, la condición B no negada y negada se puede agregar a la condición previa P , respectivamente. La condición B no debe tener efectos secundarios. En la siguiente sección se ofrece un ejemplo.
Esta regla no estaba contenida en la publicación original de Hoare. [1] Sin embargo, desde una declaración
tiene el mismo efecto que una construcción de bucle de una sola vez
la regla condicional se puede derivar de las otras reglas de Hoare. De manera similar, las reglas para otras construcciones de programas derivados, como for loop, do...until loop, switch , break , continue pueden reducirse mediante la transformación del programa a las reglas del artículo original de Hoare.
Esta regla permite fortalecer la precondición y/o debilitar la poscondición . Se utiliza, por ejemplo, para lograr condiciones posteriores literalmente idénticas para la parte then y else .
Por ejemplo, una prueba de
necesita aplicar la regla condicional, lo que a su vez requiere demostrar
para la parte entonces , y
por la otra parte.
Sin embargo, la regla de asignación para la parte entonces requiere elegir P como ; la aplicación de la regla por lo tanto produce
La regla de consecuencia es necesaria para fortalecer la condición previa obtenida de la regla de asignación a lo requerido por la regla condicional.
De manera similar, para la otra parte, la regla de asignación produce
por lo tanto, la regla de la consecuencia debe aplicarse con y siendo y , respectivamente, para fortalecer nuevamente la condición previa. Informalmente, el efecto de la regla de consecuencia es "olvidar" lo que se conoce en la entrada de la parte else , ya que la regla de asignación utilizada para la parte else no necesita esa información.
Aquí P es el invariante del bucle , que debe ser preservado por el cuerpo del bucle S. Una vez finalizado el ciclo, esta P invariante aún se mantiene y, además, debe haber causado que el ciclo termine. Como en la regla condicional, B no debe tener efectos secundarios.
Por ejemplo, una prueba de
por el momento la regla requiere probar
que se obtiene fácilmente mediante la regla de asignación. Finalmente, la poscondición se puede simplificar a .
Para otro ejemplo, la regla while se puede utilizar para verificar formalmente el siguiente extraño programa para calcular la raíz cuadrada exacta x de un número arbitrario a , incluso si x es una variable entera y a no es un número cuadrado:
Después de aplicar la regla while siendo P verdadera , queda por demostrar
que se sigue de la regla de omisión y de la regla de consecuencia.
De hecho, el extraño programa tiene parte de razón: si termina, es seguro que x debe haber contenido (por casualidad) el valor de la raíz cuadrada de a . En los demás casos no terminará; por lo tanto no es del todo correcto.
Si la regla while ordinaria anterior se reemplaza por la siguiente, el cálculo de Hoare también se puede utilizar para demostrar la corrección total , es decir, la terminación y la corrección parcial. Comúnmente, aquí se utilizan corchetes en lugar de llaves para indicar la noción diferente de corrección del programa.
En esta regla, además de mantener el bucle invariante, también se prueba la terminación mediante una expresión t , llamada variante del bucle , cuyo valor disminuye estrictamente con respecto a una relación bien fundada < en algún conjunto de dominios D durante cada iteración. Dado que < está bien fundamentado, una cadena estrictamente decreciente de miembros de D sólo puede tener una longitud finita, por lo que t no puede seguir disminuyendo eternamente. (Por ejemplo, el orden habitual < está bien fundamentado en números enteros positivos , pero no en números enteros ni en números reales positivos ; todos estos conjuntos están pensados en el sentido matemático, no en el computacional, todos son infinitos en particular.)
Dado el invariante del bucle P , la condición B debe implicar que t no es un elemento mínimo de D , porque de lo contrario el cuerpo S no podría disminuir t más, es decir, la premisa de la regla sería falsa. (Esta es una de varias notaciones para una total corrección). [nota 3]
Retomando el primer ejemplo de la sección anterior, para una prueba de total corrección de
se puede aplicar la regla while para la corrección total, siendo, por ejemplo, D los números enteros no negativos con el orden habitual y siendo t la expresión , lo que a su vez requiere demostrar
Hablando informalmente, tenemos que demostrar que la distancia disminuye en cada ciclo del bucle, mientras que siempre permanece no negativa; este proceso sólo puede continuar durante un número finito de ciclos.
El objetivo de prueba anterior se puede simplificar a
lo cual se puede demostrar de la siguiente manera:
Para el segundo ejemplo de la sección anterior, por supuesto, no se puede encontrar ninguna expresión t que sea disminuida por el cuerpo del bucle vacío, por lo que no se puede probar la terminación.