La lógica de Hoare (también conocida como lógica 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 informáticos . Fue propuesta en 1969 por el informático y lógico británico Tony Hoare , y posteriormente refinada 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 el triple de Hoare . Un triple describe cómo la ejecución de un fragmento de código cambia el estado del cómputo. Un triple de Hoare tiene la forma
donde y son afirmaciones y es un comando . [nota 1] se denomina condición previa y condición posterior : cuando se cumple la condición previa, la ejecución del comando establece la condición posterior. Las afirmaciones son fórmulas en la 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, Hoare y muchos otros investigadores han desarrollado reglas para otras construcciones del lenguaje desde entonces. Hay reglas para concurrencia , procedimientos , saltos y punteros .
Usando la lógica estándar de Hoare, solo se puede probar 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 extendida de la regla While. [3] Por lo tanto, la lectura intuitiva de una tripleta de Hoare es: Siempre que se cumple del estado antes de la ejecución de , entonces se cumplirá después, o no termina. En el último caso, no hay "después", por lo que puede haber cualquier enunciado. De hecho, uno puede elegir que sea falso para expresar 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 terminará eventualmente, es decir, implica la ausencia de bucles infinitos; no implica la ausencia de violaciones de 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 límites de implementación, y expresó su preferencia por la noción más amplia de terminación ya que mantiene las aserciones independientes de la implementación: [4]
Otra deficiencia de los axiomas y reglas citados anteriormente es que no dan ninguna base para demostrar que un programa termina con éxito. El hecho de que no se termine puede deberse a un bucle infinito; o puede deberse a la violación de un límite definido por la implementación, por ejemplo, el rango de operandos numéricos, el tamaño de almacenamiento o un límite de tiempo del sistema operativo. Por lo tanto, la notación “ ” debe interpretarse como “siempre que el programa termine con éxito, las propiedades de sus resultados se describen mediante ”. Es bastante fácil adaptar los axiomas de modo que no se puedan usar 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 los bucles infinitos, probablemente sea mejor demostrar la corrección “condicional” de un programa y confiar en que una implementación dé una advertencia si ha tenido que abandonar la ejecución del programa como resultado de la violación de un límite de implementación.
La regla de declaración vacía afirma que la declaración skip no cambia el estado del programa, por lo tanto, todo lo que es verdadero antes de skip también es verdadero 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 de P después de la asignación . Por lo tanto, si fuera verdadera antes de la asignación, según el axioma de asignación, entonces P sería verdadera después de la asignación. Por el contrario, si fuera falsa (es decir, verdadera) antes del enunciado de asignación, P debe ser falsa después.
Algunos ejemplos de triples válidos incluyen:
Todas las precondiciones que no se modifican con la expresión se pueden trasladar a la poscondición. En el primer ejemplo, la asignación no cambia el hecho de que , por lo que ambas afirmaciones pueden aparecer en la poscondición. Formalmente, este resultado se obtiene aplicando el esquema axiomático con P siendo ( y ), lo que da como resultado ( y ), que a su vez se puede simplificar a la precondición 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 ocurrencias del lado izquierdo de la asignación con el lado derecho de la asignación. Tenga cuidado de no intentar hacer esto al revés siguiendo esta forma incorrecta de pensar: ; esta regla conduce a ejemplos sin sentido como:
Otra regla incorrecta que parece tentadora a primera vista es que conduce a ejemplos sin sentido como:
Si bien una determinada poscondición P determina de manera única la precondición , lo inverso no es cierto. Por ejemplo:
son instancias válidas 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 ( alias ), aunque es una instancia apropiada del esquema del axioma de asignación ( siendo y ambos ).
La regla de composición de Hoare se aplica a los programas ejecutados secuencialmente S y T , donde S se ejecuta antes que T y se escribe ( Q se denomina condición intermedia ): [5]
Por ejemplo, considere las siguientes dos instancias 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 las partes then y else es también una poscondición de toda la declaración if...endif . [6] En las partes then y else , la condición no negada y negada B se pueden agregar a la precondición P , respectivamente. La condición, B , no debe tener efectos secundarios. Se da un ejemplo en la siguiente sección.
Esta regla no estaba contenida en la publicación original de Hoare. [1] Sin embargo, dado que 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 se pueden reducir mediante la transformación del programa a las reglas del artículo original de Hoare.
Esta regla permite reforzar la condición previa y/o debilitar la condición posterior . 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, que a su vez requiere probar
para la parte de entonces , y
para la parte demás .
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 requerida para la regla condicional.
De manera similar, para la parte else , la regla de asignación produce
Por lo tanto, la regla de consecuencia debe aplicarse con y siendo y , respectivamente, para reforzar nuevamente la condición previa. De manera informal, el efecto de la regla de consecuencia es "olvidar" 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 conservarse mediante el cuerpo del bucle S. Una vez finalizado el bucle, este invariante P sigue siendo válido y, además, debe haber provocado la finalización del bucle. Como en la regla condicional, B no debe tener efectos secundarios.
Por ejemplo, una prueba de
por la regla while se requiere probar
que se obtiene fácilmente mediante la regla de asignación. Finalmente, la poscondición se puede simplificar a .
Como otro ejemplo, la regla while se puede utilizar para verificar formalmente el siguiente programa extraño 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 con P siendo verdadero , queda demostrar
lo cual se desprende de la regla de salto y de la regla de consecuencia.
De hecho, el extraño programa es parcialmente correcto: si por casualidad terminara, es seguro que x debe haber contenido (por casualidad) el valor de la raíz cuadrada de a. En todos los demás casos, no terminará; por lo tanto, no es totalmente 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, así como 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 invariante el bucle, también se prueba la terminación por medio de una expresión t , llamada variante del bucle , cuyo valor decrece estrictamente con respecto a una relación bien fundada < en algún conjunto del dominio D durante cada iteración. Como < está bien fundada, una cadena estrictamente decreciente de miembros de D solo puede tener una longitud finita, por lo que t no puede seguir decreciendo eternamente. (Por ejemplo, el orden usual < está bien fundado en los números enteros positivos , pero no en los números enteros ni en los números reales positivos ; todos estos conjuntos se entienden en el sentido matemático, no en el computacional, son todos infinitos en particular).
Dado el invariante de bucle P , la condición B debe implicar que t no es un elemento mínimo de D , ya que 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 las diversas notaciones para la corrección total.) [nota 3]
Retomando el primer ejemplo de la sección anterior, para una prueba de corrección total de
La regla while para la corrección total se puede aplicar con, por ejemplo, D siendo los números enteros no negativos con el orden habitual, y la expresión t siendo , que a su vez requiere demostrar
Hablando informalmente, tenemos que demostrar que la distancia disminuye en cada ciclo del bucle, aunque siempre permanece no negativa; este proceso solo 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 tanto no se puede probar la terminación.