C APÍTULO 13 La Programación Imperativa Índice del Capítulo 13.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 13.2. Especificaciones de programas . . . . . . . . . . . . . . . . . . . . . . . . 220 13.3. La sentencia skip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 13.4. La sentencia abort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 13.5. La sentencia de asignación . . . . . . . . . . . . . . . . . . . . . . . . . . 222 13.5.1. Concatenación o composición . . . . . . . . . . . . . . . . . . . . . 225 13.6. Cálculo de partes de asignaciones . . . . . . . . . . . . . . . . . . . . . . 226 13.7. Sentencias y expresiones condicionales . . . . . . . . . . . . . . . . . . . . 228 13.7.1. La sentencia alternativa . . . . . . . . . . . . . . . . . . . . . . . . . 229 13.8. Expresiones condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 13.9. Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 13.1. Introducción A partir de ahora cambiaremos el modelo computacional y por lo tanto el formalismo para expresar los programas. Todo lo hecho hasta ahora nos será de utilidad en lo que sigue, ya que la experiencia adquirida en el cálculo de programas, y la consecuente habilidad para el manejo de fórmulas, seguirá siendo escencial en este caso, mostrando así que pese a sus diferencias, la programación funcional y la imperativa tienen varias cosas en común. 219 220 13. L A P ROGRAMACIÓN I MPERATIVA 13.2. Especificaciones de programas Vamos ahora a presentar algunas de las aplicaciones del cálculo de predicados en computación: las especificaciones formales. En el estilo de la programación imperativa, la computación no se expresa como cálculo de valores como vimos en el modelo de la programación funcional, sino como modificación de estados. Para desarrollar programas imperativos, es por lo tanto deseable expresar las especificaciones como relaciones entre estados iniciales y finales. Así es que un problema a resolver en el modelo de la programación imperativa estará especificado por un espacio de estados, una precondición y una poscondición. El espacio de estados es el conjunto de valores que pueden tomar las variables. La pre y poscondición son predicados que expresan el problema dado en términos precisos. La notación que usaremos para expresar la especificación de un programa imperativo es: {P } S {Q} donde P y Q representan a los predicados que describen la pre y poscondición del programa respectivamente y S es un programa, también llamado sentencia, instrucción o comando. La terna {P } S {Q} se denomina tripleta de Hoare y tiene el siguiente significado: Siempre que se ejecuta S comenzando en un estado que satisface P , se termina en un estado que satisface Q. Recordemos que vimos en el Capítulo 1 el concepto de tripleta de Hoare {P } S {Q} cuando S es una asignación. Podemos afirmar, entonces que derivar un programa consiste en determinar S de manera que satisfaga las especificaciones dadas. Por ejemplo, derivar un programa que satisfaga {P } S {x = y}, consiste en encontrar S tal que, después de su ejecución sobre un estado que satisface P se obtiene un estado que satisface x = y. En los lenguajes imperativos, es necesario explicitar el tipo de las variables y constantes involucradas en el programa, lo cual se hace a través de una declaración de variables y constantes. Esto completa la especificación del programa a la vez que indica implícitamente las operaciones que se pueden realizar con las variables y constantes declaradas. Así es que un problema a resolver en el modelo de la programación imperativa estará especificado por un espacio de estados, una precondición y una poscondición. El espacio de estados es el conjunto de valores que pueden tomar las variables. La pre y poscondición son predicados que expresan el problema dado en términos precisos. Por ejemplo, consideremos las especificaciones de un programa que calcula el máximo común divisor entre dos números enteros positivos X e Y, 13.2. E SPECIFICACIONES DE PROGRAMAS 221 |[var x, y : Int cons X,Y : Int {X > 0 ∧ Y > 0 ∧ x = X ∧ y = Y} S {x = mcd.X.Y} ]| La primera línea define el espacio de estados Z × Z. La segunda línea corresponde a la precondición del programa, las variables X e Y son llamadas variables rígidas, no son variables del programa y por tanto no deben aparecer en las sentencias del programa. La tercera línea corresponde a las sentencias del programa especificado y finalmente la cuarta contiene a la poscondición del programa. La interpretación operativa de las especificaciones es la que sigue: el programa S satisface las especificaciones, si para todos los enteros X e Y, la ejecución de S en un estado que satisface {X > 0 ∧ Y > 0 ∧ x = X ∧ y = Y}, termina en un estado que satisface {x = mcd.X.Y}. Antes de continuar con el desarrollo de programas, veremos algunas reglas para la correcta interpretación y utilización de {Q} S {R}, aún cuando S no sea una asignación. La primera regla, conocida como ley de exclusión de milagros, expresa: {P } S {f alse} ≡ P ≡ f alse Esta regla establece que para cualquier programa S, si se requiere que termine y que los estados finales satisfagan f alse, entonces ese programa no puede ejecutarse exitosamente para ningún posible estado inicial. La regla de exclusión de milagros establece que, cada vez que se ejecuta S comenzando en un estado que satisface P y terminando en uno que satisface f alse, (es decir en ningún estado), P sea equivalente a f alse. La expresión {P } S {true} indica que, cada vez que comienza en un estado que satisface P , la ejecución de S concluye en un estado que satisface el predicado true, es decir, la ejecución de S termina cada vez que se comienza en un estado que satisface P . Otra regla que introducimos a continuación está relacionada con el hecho de que una precondición puede ser fortalecida y una poscondición puede ser debilitada, lo cual formulamos de la siguiente manera: {P } S {Q} ∧ (Po ⇒ P ) ⇒ {Po } S {Q} {P } S {Q} ∧ (Q ⇒ Qo ) ⇒ {P } S {Qo } Supongamos que las tripletas {P } S {Q} y {P } S {R} son válidas. Entonces la ejecución de S en un estado que satisface P termina en uno que satisface Q y R, entonces termina en un estado que satisface Q ∧ R. Esta regla se exprersa de la siguiente manera: 222 13. L A P ROGRAMACIÓN I MPERATIVA {P } S {Q} ∧ {P } S {R} ≡ {P } S {Q ∧ R} Por último, la regla que sigue 13.5. L A 223 SENTENCIA DE ASIGNACIÓN Esta definición fue dada bajo el supuesto de que E es total, es decir, E asume un valor en cualquier estado. Muchas expresiones no son totales, por ejemplo, 10/x está definida sólo si x 6= 0, y la referencia de lista c [i] está definida sólo si i está dentro del rango de la lista c. Para cada expresión E, definimos el predicado {P } S {Q} ∧ {R} S {Q} ≡ {P ∨ R} S {Q} dom.E que se satisface exactamente en aquellos estados para los cuales E está definida. (dom.E proviene de “dominio” de E). Veamos algún ejemplo: 13.3. La sentencia skip La primer sentencia que consideramos es la sentencia skip. La ejecución de skip no produce cambios sobre los estados de las variables. La interpretación operativa de esta sentencia en términos de la tripleta de Hoare es p dom. x/y ≡ y 6= 0 ∧ x/y ≥ 0. Damos ahora una definición más general de asignación: {dom.E ∧ R [x := E]} {P } skip {P } Ahora como la precondición puede ser fortalecida entonces también podemos caracterizar esta sentencia mediante: {P } skip {Q} ≡ P ⇒ Q Es decir, la precondición X más débil que hace válida la tripleta {X} skip {Q} es Q. Por ejemplo, la tripleta {x ≥ 1} skip {x ≥ 0} es válida pues x ≥ 1 ⇒ x ≥ 0. {R} . (13.1) En lo que sigue, omitiremos dom.E en la precondición cuando tratemos la sentencia de asignación de modo de simplificar la escritura. Afirmaremos sin demostrarlo que R [x := E] es la precondición más débil que al ejecutar x := E termina con el resultado R ≡ true. Esto significa que otra precondición, digamos Q, satisface {Q} x := E {R} si y sólo si Q ⇒ R [x := E]. Por lo tanto, tenemos la siguiente regla para la sentencia de asignación: (13.1) Método de prueba. {Q} x := E {R} ≡ Q ⇒ R [x := E]. Consideremos la especificación {x > 0} x :=? {x > 1}. Queremos probar que esta especificación es implementada por la asignación x := x + 1, entonces probamos: 13.4. La sentencia abort x > 0 ⇒ (x > 1) [x := x + 1] , La interpretación operativa de esta sentencia está dada por la siguiente regla: {P } abort {Q} ≡ P ≡ f alse Después de esta regla, cómo se ejecuta abort?. La expresión de arriba establece que este comando nunca debe ejecutarse, ya que sólo puede hacerse en un estado que satisface f alse, y no existe tal estado. Si la ejecución de un programa alcanza alguna vez un punto en el que abort deba ejecutarse, entonces el programa completo es un error, y termina prematuramente. (x > 1) [x := x + 1] hdefinición de sustitucióni x+1>1 = haritméticai x>0 = Veamos una situación diferente: Encontrar una expresión E de modo que satisfaga Cualquier cambio de estado que ocurra durante la ejecución de un programa se debe a la sentencia de asignación. En el capítulo 1, definimos asignación múltiple x := E mediante el axioma x := E suponiendo el antecedente y probando el consecuente: La última línea es el antecedente que habíamos supuesto. 13.5. La sentencia de asignación {R [x := E]} x := E {R} . {true} x, y := x + 1, E {y = x + 1} Para lo cual E debe satisfacer: true ⇒ (y = x + 1) [x, y := x + 1, E] , 224 13. L A P ROGRAMACIÓN I MPERATIVA lo cual significa que E debe cumplir: 13.5. L A Ahora probaremos que se verifica 0 ≤ i ≤ n ∧ i = I + 1, es decir, vale: true ⇒ (y = x + 1) [x, y := x + 1, E] hCálculo de predicados (true ⇒ R) ≡ Ri (y = x + 1) [x, y := x + 1, E] = hsustitucióni E =x+1+1 = haritméticai E =x+2 P ∧ I = i 6= n ⇒ (0 ≤ i ≤ n ∧ i = I + 1) [x, i := x + b [i] , i + 1] = Nuevamente, suponemos el antecedente y probamos el consecuente: Ahora supongamos un algoritmo que suma los elementos de la lista b [0, . . . , n − 1]. Consideremos el siguiente predicado X P :0≤i≤ n∧x = k : 0 ≤ k < i : b [k] que establece que x es la suma de los primeros i elementos de b [0, . . . , n − 1]. Queremos probar que x, i :=? (0 ≤ i ≤ n ∧ i = I + 1) [x, i := x + b [i] , i + 1] hSustitucióni 0≤i+1≤n∧i+1=I+1 = hSuposición de i = I y (3.39) Neutro de ∧i 0≤i+1≤n = hSuposición de i 6= n y aritmética i 0≤i≤n = Es decir, E debe ser una expresión aritmética que evaluada en cualquier estado resulte equivalente a la expresión aritmética x + 2, lo que significa que podemos dar como respuesta esta última expresión. {P ∧ I = i 6= n} 225 SENTENCIA DE ASIGNACIÓN {P ∧ i = I + 1} (13.2) se implementa mediante x, i := x + b [i] , i + 1. Primero, discutiremos el contexto en el cual la especificación (13.2) puede aparecer. Esta especifica el cuerpo de un bucle que acumula la suma de los elementos de b [0, . . . , n − 1]. El requerimiento correspondiente a que el valor de i se incremente en una unidad asegura que el bucle progresa hacia su terminación. El requerimiento acerca de que P se mantiene, es decir si es true antes de la iteración, también lo será luego, asegura que cuando finaliza el bucle, x contiene la suma de los primeros n elementos de b. Probaremos ahora, que x, i := x + b [i] , i + 1 verifica la segunda conjunción de P , P x = ( k : 0 ≤ k < i : b [k]), es decir: X P ∧ I = i 6= n ⇒ (x = ( k : 0 ≤ k < i : b [k])) [x, i := x + b [i] , i + 1] La prueba asegura el antecedente y prueba el consecuente: P (x = ( k : 0 ≤ k < i : b [k])) [x, i := x + b [i] , i + 1] = hSutitucióni P x + b [i] = ( k : 0 ≤ k < i + 1 : b [k]) = hSeparación P de término (5.21)i x + b [i] = ( k : 0 ≤ k < i : b [k]) + b [i] = hSuposición de P i x + b [i] = x + b [i] = h(3.3) Neutro de ≡i true La última línea es una conjunción de la suposición P . 13.5.1. Concatenación o composición La concatenación permite escribir una secuencia de instrucciones. La ejecución de dos sentencias S y T , una a continuación de la otra se indicará separando las mismas con punto y coma: S; T , siendo la de la izquierda la primera que se ejecuta. Para demostrar {P } S; T {Q}, hay que encontrar un predicado intermedio R que sirva como poscondición de S y precondición de T , es decir: {P } S; T {Q} ≡ (∃R :: {P } S {R} ∧ {R} T {Q}) La precondición más débil de la concatenación S; T con respecto al predicado Q, se obtiene tomando primero la precondición más débil de T con respecto a Q y luego la precondición más débil de S con respecto a este último predicado. Por ejemplo, supongamos que queremos encontrar la precondición más débil tal que la ejecución de la secuencia x := E y y := F de asignaciones termine con R ≡ true: {?} x := E; y := F {R} Sabemos cómo encontrar la precondición más débil tal que y := F termine en R ≡ true: R [y := F ], entonces podemos encontrar la precondición más débil de modo que la ejecución de x := E verifique R [y := F ], que es R [y := F ] [x := E]. Ilustraremos esto a continuación, la primera columna presenta dos asignaciones y la poscondición, la segunda muestra el cálculo de la sentencia intermedia y la tercera presenta la precondición calculada: x := E y := F {R} x := E {R [y := F ]} y := F {R} {R [y := F ] [x := E]} x := E {R [y := F ]} y := F {R} 226 13. L A P ROGRAMACIÓN I MPERATIVA Este método puede generalizarse para encontrar la precondición más débil correspondiente a una secuencia de asignaciones de modo de verificar R: {R [xn := En ] . . . R [x2 := E2 ] R [x1 := E1 ]} x1 := E1 ; x2 := E2 ; . . . ; xn := En {R} (x = X ∧ y = Y) [y := t] [x := y] [t := x] hSustitucióni (x = X ∧ t = Y) [x := y] [t := x] = hSustitucióni (y = X ∧ t = Y) [t := x] = hSustitucióni (y = X ∧ x = Y) = Veamos otro ejemplo, queremos resolver e en n o X P2 : x = ( k : i ≤ k ≤ n : b [k]) i, x := i − 1, e {P 2} P 2 [i, x := i − 1, e] hDefinición de P 2 Sustitucióni P e = ( k : i − 1 ≤ k ≤ n : b [k]) = hSeparación dePtérmino (5.21)i e = b [i − 1] + ( k : i ≤ k ≤ n : b [k]) = hSuposición de P 2i e = b [i − 1] + x = Con lo cual podemos usar la expresión b [i − 1] + x por e. Existe otra forma de resolver e en Hemos probado que la secuencia de asignaciones intercambia x con y. 13.6. Cálculo de partes de asignaciones Supongamos que queremos mantener: X P1 : x = ( k : 0 ≤ k ≤ i : b [k]) usando como asignación i, x := i + 1, e donde e es un valor desconocido. Veremos cómo podemos obtener e a través de un cálculo, en vez de obtenerlo por tanteo. Queremos resolver e en: i, x := i + 1, e 227 DE PARTES DE ASIGNACIONES Para ello debemos resolver e en P 2 ⇒ P 2 [i, x := i − 1, e]. Por ejemplo, busquemos la precondición más débil de modo que la ejecución de t := x; x := y; y := t verifique x = X ∧ y = Y, entonces: {P 1} 13.6. C ÁLCULO {P 1} Esta tripleta de Hoare es válida exactamente cuando P 1 ⇒ P 1 [i, x := i + 1, e], de modo que debemos resolver esta expresión booleana para e. Supondremos para esto el antecedente y probaremos el consecuente: P 1 [i, x := i + 1, e] = hDefinición de P 1 y Sustitucióni P e = ( k : 0 ≤ k ≤ i + 1 : b [k]) = hSeparación de término (5.21)i P e = ( k : 0 ≤ k ≤ i : b [k]) + b [i + 1] = hSuposición de P 1i e = x + b [i + 1] Por lo tanto, podemos usar la expresión x + b [i + 1] por e. P ⇒ P [i, x := f, e] donde f es una expresión en i. Observemos que el consecuente tiene la misma estructura que el antecedente, salvo que, mientras el antecedente tiene variables como i y x el consecuente tiene expresiones. Por lo tanto, si podemos manipular el antecedente P hasta que tenga la estructura necesaria que contenga expresiones en donde se encontraban i y x, habremos identificado e. Veamos un ejemplo, consideremos la función F definida como: F. 0 = 0 F. 1 = 1 F (2 × n) = F.n para n par y mayor que 0 F (2 × n + 1) = F.n + F (n + 1) para n impar y mayor que 0 Consideremos el predicado P : C = a × F.n + b × F (n + 1) y supongamos que queremos encontrar d y e de modo que satisfagan {P ∧ n > 0 ∧ even.n} n, a, b := n ÷ 2, d, e {P } Lo cual exige resolver d y e en P ∧ n > 0 ∧ even.n ⇒ P [n, a, b := n ÷ 2, d, e]. Esto último puede reescribirse de acuerdo al teorema 3.64 (p ∧ q ⇒ r ≡ p ⇒ (q ⇒ r)) de la siguiente manera: n > 0 ∧ even.n ⇒ (P ⇒ P [n, a, b := n ÷ 2, d, e]) Supondremos el antecedente n > 0 ∧ even.n. Para simplificar los cálculos, llamemos k = n ÷ 2. Trabajaremos sobre P con el objeto de llegar a una expresión con la misma estructura pero con n ÷ 2 en vez de n. 228 13. L A P ROGRAMACIÓN I MPERATIVA P = = = = = 13.7. S ENTENCIAS (13.3) hDefinición de P i C = a · F.n + b · F (n + 1) hSuposición de even.n por lo tanto n = 2 · ki C = a · F (2 × k) + b × F (2 × k + 1) hDefinición de F dos vecesi C = a × F.k + b × (F.k + F.(k + 1)) hAritméticai C = (a + b) × F.k + b × F.(k + 1) hDefinición de k; Sustitucióni P [n, a, b := n ÷ 2, a + b, b] Y EXPRESIONES CONDICIONALES 229 Ejemplo. Queremos probar: {true} if B then skip else x, y := y, x {x ≤ y} Usamos el método de prueba 13.2, es decir debemos probar: {true ∧ x ≤ y} skip {x ≤ y} {true ∧ ¬(x ≤ y)} x, y := y, x {x ≤ y} Por lo tanto, hemos resuelto d y e, y la asignación deseada es n, a, b := n ÷ 2, a + b, b que puede escribirse como n, a := n ÷ 2, a + b. cuya demostración es inmediata. 13.7.1. La sentencia alternativa La sentencia if B then S1 else S2 puede escribirse en la notación de guardas como la sentencia alternativa siguiente: 13.7. Sentencias y expresiones condicionales La sentencia condicional, llamada IF , tiene la siguiente forma en muchos lenguajes de programación imperativos: IF : if B then S1 else S2 (13.3) donde B es una expresión booleana y S1 y S2 son sentencias. La sentencia condicional se ejecuta de la siguiente forma: Si B es true entonces se ejecuta S1, de lo contrario se ejecuta S2. Supongamos que queremos ejecutar una sentencia condicional que comenzó en un estado que satisface el predicado {Q} para luego satisfacer el predicado {R}, es decir : {Q} IF {R}. ¿Qué debe ocurrir para poder garantizar que {Q} IF {R} es válida? Si B es true, entonces debe ejecutarse S1, y por lo tanto la ejecución de S1 debe verificar R. Por otra parte si B es f alse debe ejecutarse S2, y la ejecución de S2 debe verificar R. Escribiremos esto de modo de ilustrar la sentencia condicional IF y además indicar que B y ¬B pueden ser supuestos antes de S1 y S2 respectivamente: {Q} if B then {Q ∧ B} S1 {R} else {Q ∧ ¬B} S2 {R} {R} Por lo tanto, tenemos el siguiente: (13.2) if B → S1 [] ¬B → S2 fi Método de prueba. Método de Prueba para IF : Para probar {Q} IF {R}, es suficiente con probar {Q ∧ B} S1 {R} y {Q ∧ ¬B} S2 {R}. Un comando de la forma B → S se llama comando protegido o resguardado. B es la guarda o protección en la entrada →, que asegura se ejecutará el comando S sólo cuando sea apropiado. En la notación de comando de guarda, una sentencia alternativa puede escribirse con más de dos posibilidades. Por ejemplo, a continuación presentamos una sentencia alternativa que llamaremos IF G, con tres comandos guarda: if B1 → S1 [] ¬B2 → S2 [] ¬B3 → S3 fi La ejecución de una sentencia alternativa se realiza de la siguiente manera: si ninguno de las guardas es true, la ejecución aborta. Abortar significa terminar prematuramente. Cuando un programa aborta, no se conoce lo que pueda ocurrir, aunque una buena implementación siempre dará un mensaje de error y detendrá la ejecución. Si al menos una guarda es true, entonces se elige una de éstas y se ejecuta su comando correspondiente. Existen dos claves para la sentencia alternativa: • La ejecución aborta si ninguna guarda es true. • Si más de una guarda es true, se elige una entre ellas arbitrariamente, y su correspondiente comando se ejecuta a continuación. 230 13. L A P ROGRAMACIÓN I MPERATIVA Si más de una guarda es true, la sentencia alternativa se dice no determinística. El “nodeterminismo”, es útil para escribir con claridad determinados programas y también para permitir la simetría. Por ejemplo, en el siguiente programa no es importante cuál entre x e y es guardado en z cuando x = y, con lo cual el programador no necesita hacer la elección: if x ≤ y → z := y [] y ≤ x → z := x fi {z es el máximo entre x e y} Otro ejemplo, aquí presentamos un algoritmo que ordena las variables w, x, y, z intercambiando repetidamente sus valores, sería mucho más engorroso escribir este programa sin nodeterminismo: while ¬(w ≤ x ≤ y ≤ z) if w>x [] x>y [] y>x fi {w ≤ x ≤ y ≤ z} do → w, x := x, w → x, y := y, x → y, z := z, y Para probar {Q} IF G {R} es suficiente probar que: (i) cuando comienza la ejecución, al menos una guarda es true y (ii) cada comando verifica R. (13.4) Método de prueba. Método de prueba para IF G: Pra probar {Q} IF G {R} es suficiente probar que: (a) Q ⇒ B1 ∨ B2 ∨ B3, 13.7. S ENTENCIAS 231 Y EXPRESIONES CONDICIONALES (a) x = 0 ⇒ true ∨ true, (b) {x = 0 ∧ true} x := 1 {x = 1 ∨ x = −1}, (c) {x = 0 ∧ true} x := −1 {x = 1 ∨ x = −1}. El apartado (a) sigue inmediatamente del teorema P ⇒ true ≡ true. Para demostrar (b), tenemos que demostrar que x = 0 ∧ true ⇒ (x = 1 ∨ x = −1)[x := 1] Comenzaremos con el consecuente: (x = 1 ∨ x = −1)[x := 1] hSustitucióni 1 = 1 ∨ 1 = −1 = h(1 = 1) ≡ true y además true es absorbente para ∨i true ⇐ hPor apartado (a) x = 0 ⇒ truei x=0 = htrue neutro de ∧i x = 0 ∧ true = El apartado (c) se resuelve en forma similar. Veamos otra situación. Queremos determinar S de modo que se satisfaga: {true} S {z = max(x, y)} Vamos a expresar el máximo entre x e y en una forma que sea más fácil de manipular: (b) {Q ∧ B1} S1 {R}, (c) {Q ∧ B2} S2 {R} y z = max(x, y) ≡ (z = x ∨ z = y) ∧ z ≥ x ∧ z ≥ y (d) {Q ∧ B3} S3 {R}. Esto significa que una candidato posible para el máximo es el valor de x, por tanto una instrucción posible a ejecutar es z := x, veamos entonces cuál es la precondición más débil para la sentencia de asignación z := x respecto del predicado z = max(x, y): Este método se extiende obviamente a comandos alternativos con más de tres o con menos de tres comandos guarda. Veamos ahora algunos ejemplos. Supongamos que queremos demostrar la corrección del siguiente programa: {x = 0} if true → x := 1 [] true → x := −1 fi {x = 1 ∨ x = −1} Utilizando el método de prueba 13.4, debemos probar: ((z = x ∨ z = y) ∧ z ≥ x ∧ z ≥ y)[z := x] hSustitucióni (x = x ∨ x = y) ∧ x ≥ x ∧ x ≥ y) = h(x = x) ≡ true y además (x ≥ x) ≡ truei (true ∨ x = y) ∧ true ∧ x ≥ y) = htrue neutro de ∧ y absorbente de ∨i x≥y = Lo cual demuestra que si elegimos el predicado x ≥ y, la tripleta {x ≥ y} es válida. z := x {z = max(x, y)} 232 13. L A P ROGRAMACIÓN I MPERATIVA Por simetría también será válida {y ≥ x} Además, observando que z := y {z = max(x, y)}. x≥y∨y ≥x haritméticai x ≥ y ∨ y > x) = h tercero excluido p ∨ ¬p ≡ truei true ⇐ es fácil ver que true ⇒ x ≥ y ∨ y ≥ x Por lo tanto y utilizando el método de prueba 13.4, podemos afirmar que la sentencia S puede ser: if x ≥ y → z := x [] y ≥ x → z := y fi Ahora presentamos otra situación, supongamos que debemos demostrar la corrección del siguiente programa: |[var x, y : Int {true} x, y := y × y, x × x if x ≥ y → x := x − y [] y ≥ x → y := y − x fi {x ≥ 0 ∧ y ≥ 0} Observemos que aparecen dos sentencias concatenadas, la primera es una asignación y la segunda es una sentencia alternativa, por lo tanto, debemos encontra un predicado intermedio Q que sea la poscondición de la asignación y a su vez la precondición de la alternativa. Comenzamos buscando entonces la precondición más débil respecto de la poscondición {x ≥ 0 ∧ y ≥ 0}. Utilizando el método de prueba 13.4, debemos encontar Q tal que: (a) Q ⇒ x ≥ y ∨ y ≥ x, (b) {Q ∧ x ≥ y} x := x − y {x ≥ 0 ∧ y ≥ 0}, (c) {Q ∧ y ≥ x} y := y − x {x ≥ 0 ∧ y ≥ 0}. El apartado (a) sigue inmediatamente del teorema Q ⇒ true ≡ true. Los apartados (b) y (c) imponen condiciones sobre el predicado Q. Comencemos con (b): el método de prueba para asiganciones, exige que Q satisfaga: Q ∧ x ≥ y ⇒ (x ≥ 0 ∧ y ≥ 0)[x := x − y], 13.8. E XPRESIONES CONDICIONALES 233 lo cual equivale a calcular primero la precondición más débil respecto de la asignación x := x − y (x ≥ 0 ∧ y ≥ 0)[x := x − y] hsustitucióni x−y ≥ 0∧y ≥0 = haritméticai x≥ y∧y ≥0 = Observemos que si suponemos el antecedente Q ∧ x ≥ y, en la última línea, obtenemos x≥ y∧y ≥0 hsuponiendo el antecedente x ≥ y ≡ truei true ∧ y ≥ 0 = htrue neutro de ∧i y≥0 = Así conseguimos que Q ⇒ y ≥ 0. En forma similar y de acuerdo al apartado (c), Q debe satisfacer Q ⇒ x ≥ 0. Resumiendo, hemos obtenido que Q es cualquier expresión booleana que implique x ≥ 0 ∧ y ≥ 0. Podemos considerar obviamente a ésta como Q. Ahora falta ver que true ⇒ Q[y, x := x × x, y × y]. Para esto recordemos que true ⇒ p ≡ p. Probemos entonces Q[y, x := x × x, y × y]: (x ≥ 0 ∧ y ≥ 0)[y, x := x × x, y × y] hsustitucióni x×x≥0∧y×y ≥0 = hteoremas de orden i true = 13.8. Expresiones condicionales Así como tenemos sentencias condicionales if B then S1 else S2, también tenemos las expresiones condicionales: if B then E1 else E2 (13.4) donde B es una expresión booleana, y E1 y E2 son expresiones del mismo tipo. Evaluar una expresión como ésta da como resultado el valor de E1 si B es true y el valor de E2 en otro caso. Veamos ejemplos de expresiones condicionales: aquí consideramos un estado en el cual x = 5, y = 4, b = true y c = f alse, en ese estado tenemos: 234 13. L A P ROGRAMACIÓN I MPERATIVA (a) (if x = y then x else x + 2) = 7 (b) (if x 6= y then x else x + 2) = 5 (c) (if b ∨ c then x × y else x + 2) = 20 (d) (if b ∧ c then x × y else x + 2) = 7 (e) (if b then c ⇒ b else b ⇒ c) = true (f) (if c then c ∨ b else b ∧ c) = f alse Existen dos reglas para manipular expresiones if-then-else: (13.5) Axioma. Condicional: B ⇒ ((if B then E1 else E2) = E1) (13.6) Axioma. Condicional: ¬B ⇒ ((if B then E1 else E2) = E2) 13.9. Ejercicios 13.1 Dar el significado operativo de las tripletas: {true} S {true} y {f alse} S {true} 13.2 Deducir a partir de las reglas dadas en la Sección 13.2 que {P0 } S {Q0 } y {P1 } S {Q1 } implican {P0 ∧ P1 } S {Q0 ∧ Q1 } y {P0 ∨ P1 } S {Q0 ∨ Q1 } 13.3 Demostrar que {f alse} S {P } es válida para cualquier S y cualquier P . 13.4 Dada una sentencia S y un predicado Q, llamaremos wp.S.Q al predicado más débil P para el cual la tripleta {P } S {Q} es válida. Este predicado wp.S.Q es llamado la precondición más débil de S respecto de Q. La relación entre {P } S {Q} y wp.S.Q es la siguiente: {P } S z {Q} ≡ P ⇒ wp.S.Q Demostrar que las reglas vistas en la sección 13.2, siguen de las siguientes reglas para wp.S: 13.9. E JERCICIOS 13.5 Demostrar que las siguientes tripletas son válidas: |[var x, y : Int {x > 0 ∧ y > 0} a) skip {x > 0} ]| |[var x, y : Int {x > 0 ∧ y > 0} b) skip {y ≥ 0} ]| |[var x, y : Bool {x ≡ y} c) skip {x ⇒ y} ]| 13.6 Demostrar que las siguientes tripletas no son válidas: |[var x, y : Int {x > 0 ∧ y > 0} a) skip {x = 1} ]| |[var x, y : Int {x > 0 ∧ y > 0} b) skip {y ≥ x} ]| |[var x, y : Bool {x ≡ y} c) skip {x ∧ y} ]| 13.7 Determinar la precondición más débil P en cada caso: a) wp.S.f alse ≡ f alse a) {P } x := x + 1 {x > 0} b) wp.S.Q ∧ wp.S.R ≡ ws.S.(Q ∧ R) b) {P } x := x ∗ x {x > 0} c) wp.S.Q ∨ wp.S.R ⇒ ws.S.(Q ∨ R) c) {P } x := x ∗ x ∗ x − 2 ∗ x + 4 {x > 0} 235 236 13. L A P ROGRAMACIÓN I MPERATIVA d) {P } x := x + 1 {x3 − 5x2 + 2x > 0} 3 13.9. E JERCICIOS 13.12 Usar el método (13.2) para probar que el siguiente programa es correcto: 2 e) {P } x := x ∗ x ∗ x − 2 ∗ x + 4 {x − 5x + 2x > 0} f) {P } x := x + 1 {x = x + 1} g) {P } x := E {x = E} h) {P } x := x mod 2 {x = x mod 2} i) {P } x, y := x + 1, y − 1 {x + y > 0} {x > 5} if x ≤ y then skip else x, y := y, x {x ≤ y} 13.13 Usar el método (13.2) para probar que el siguiente programa es correcto: j) {P } x, y := y + 1, x − 1 {x > 0} {x = X} if x < 0 then x := −x else skip {x = abs.X} k) {P } a := a ≡ b {a} l) {P } a := a ⇒ b {a ∨ b} 13.8 Calcular la precondición más débil P en cada caso: a) {P } x := x + 1; x := x + 1 {x > 0}. b) {P } x := x ∗ x; x := x + 1 {x > 0}. c) {P } x := x 6≡ y; y := x 6≡ y; x := x 6≡ y {(x ≡ X) ∧ (y ≡ Y)}. d) {P } a := a ≡ b; b := a ≡ b; a := a ≡ b {(a ≡ A) ∧ (b ≡ B)}. e) {P } x := x + y; y := x − y; x := x − y {x = X ∧ y = Y}. f) {P } x := y; y := x {x = X ∧ y = Y}. 13.14 Usar el método (13.2) para probar que el siguiente programa es correcto: {y > 0 ∧ z × xy = X} if odd.y then z, y := z × x, y − 1 else x, y := x × x, y/2 {y ≥ 0 ∧ z × xy = X} 13.15 Probar: g) {P } x := x + 1; skip {x2 − 1 = 0}. h) {P } skip; x := x + 1 {x2 − 1 = 0}. 13.9 Calcular la expresión que haga válida la tripleta en cada caso a) {A = q ∗ B + r} q := E; r := r − B {A = q ∗ B + r}. b) {true} y := E; x := x div 2 {2 ∗ x = y}. c) {x ∗ y + p ∗ q = N} x := x − p; q := E {x ∗ y + p ∗ q = N}. 13.10 Supongamos que el número de manzanas que tienen Juan y María (m y j respectivamente) están relacionadas con la siguiente fórmula (donde C es una constante): P :C=m+2×j Encontrar una solución de e en {P ∧ even.m} m, j := m ÷ 2, e {P }. 13.11 Recordemos que los números de Fibonacci F.i están dados por F. 0 = 0, F. 1 = 1 y F.n = F.(n − 1) + F.(n − 2) para n ≥ 2. El siguiente predicado define las variables n, a y b: P : n > 0 ∧ a = F.n ∧ b = F.(n − 1) Encontrar una solución para e y f en {P } n, a, b := n + 1, e, f {P }. {true} if x ≥ 1 → x := x + 1 [] x ≤ 1 → x := x − 1 a) fi {x 6= 1} {true} if x≥y → skip [] x ≤ y → x, y := y, x b) fi {x ≥ y} |[var a, b : bool {true} if ¬a ∨ b → a := ¬a c) [] a ∨ ¬b → b := ¬b fi {a ∨ b} 13.16 Encontrar el predicado P más débil que haga correcto el siguiente programa: 237 238 13. L A P ROGRAMACIÓN I MPERATIVA |[var x : Int {P } x := x + 1 if x > 0 → x := x − 1 [] x < 0 → x := x + 2 [] x=1 → skip fi {x ≥ 1} 13.17 Probar que {P } if B0 → S0 ; S [] B1 → S1 ; S fi {Q} equivale a {P } if B0 → S0 [] B1 → S1 fi S {Q}