3. CONTROL en la PROGRAMACIÓN LOGICA 45 Tema 3. CONTROL en la PROGRAMACION LOGICA 3.1. Control mediante el Orden en literales y sentencias 3.2. Control con cortes 3.2.1. Operador corte 3.2.2. Cortes verdes 3.2.3. Cortes rojos 3.3. Distintos usos del corte 3.3.1. Confirmación de la elección de una regla 3.3.2. Combinación corte-fallo 3.3.3. Generación y prueba 3.4. La negación como fallo finito Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 46 Apuntes: Curso de PROGRAMACION LOGICA 3. CONTROL en la PROGRAMACIÓN LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 47 TEMA 3. CONTROL en la PROGRAMACION LOGICA En este tema veremos aspectos del control en PROLOG. En primer lugar, cómo afecta el orden de los literales y de las sentencias en el funcionamiento del programa. Ya hemos visto cómo crear el árbol de llamadas y cómo explorarlo siguiendo una estrategia SLD. Sin embargo, muchas veces al programar y conociendo las características del problema, se puede asegurar que cierta parte del árbol no dará lugar a soluciones y por tanto no necesitaría ser explorada. Para indicar a PROLOG cuestiones de este estilo y ganar en eficiencia, disponemos del operador corte del que hablaremos en las dos siguientes secciones. Finalmente, dedicamos la última sección a estudiar la negación como fallo finito y su relación con la negación lógica. 3.1. Control mediante el Orden en literales y sentencias El orden en que aparecen los literales dentro de una sentencia (dentro del cuerpo) o el orden en que se introducen las sentencias en el programa son importantes en PROLOG. El orden afecta tanto al correcto funcionamiento del programa, como al recorrido del árbol de llamadas, determinando, entre otras cosas, el orden en que PROLOG devuelve las soluciones a una pregunta dada. El orden de las sentencias determina el orden en que se obtienen las soluciones ya que varía el orden en que se recorren las ramas del árbol de búsqueda de soluciones. Ejemplo: A continuación se presentan dos versiones del programa "miembro de una lista". Ambas versiones tienen las mismas sentencias pero escritas en distinto orden. A ambas versiones les hacemos la misma pregunta ?miembro (X, [1,2,3]). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 48 3. CONTROL en la PROGRAMACIÓN LOGICA Primera versión: Segunda versión: 1. miembro (X, [_|Z]) :- miembro (X, Z). 2. miembro (X, [X|_]). 1. miembro (X, [X|_]). 2. miembro (X, [_|Z]) :- miembro (X, Z). ?m iem bro(X , [1, 2, 3]). ?miembro(X, [1, 2, 3]). {X→1} 1. 2. (1) ?m iem bro(X , [2, 3]). {X 2→ X , Z2→ [3]} {X → 2} 1. 2. ?miembro(X, [2, 3]). EXITO X=1 {X→2} 1. (2) {X2→X, Z2→[3]} 2. ?miembro(X, [3]). EXITO X=2 {X→3} 1. {X3→X, Z3→[ ]} 2. (3) ?miembro(X, [ ]). EXITO X=3 ? miembro (X, [1, 2, 3]). X = 1; X = 2; X=3 Apuntes: Curso de PROGRAMACION LOGICA {X → 1} 2. {X 1→ X , Z1→ [2, 3]} 1. {X1→X, Z1→[2, 3]} ⊗ FRACASO EX ITO X =1 ?m iem bro(X , [3]). EX ITO {X 3→ X , Z3→ [ ]} X =2 {X 3} → 1. 2. ?m iem bro(X , [ ]). ⊗ FRACASO EX ITO X =3 ? miembro (X, [1, 2, 3]). X = 3; X = 2; X=1 Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 49 Si el árbol de búsqueda tiene alguna rama infinita, el orden de las sentencias puede alterar la obtención de las soluciones, e incluso llegar a la no obtención de ninguna solución. (Ver como ejemplo el generador de números naturales, de la sección Generación infinita en el tema anterior). Como regla heurística a seguir cuando se programa en PROLOG, es recomendable que los hechos aparezcan antes que las reglas del mismo predicado. El orden de los literales dentro de una sentencia (en el cuerpo de una regla PROLOG) afecta más profundamente al espacio de búsqueda y a la complejidad de los cómputos lógicos: Distintas opciones en el orden de los literales pueden ser preferibles para distintos modos de uso. Ejemplo: La definición del predicado hijo puede hacerse: hijo(X, Y) :- hombre(X), padre(Y, X). para modo (in, out): Se comprueba primero que el dato X es hombre y se busca a su padre Y. hijo(X, Y) :- padre(Y, X), hombre(X). para modo (out, in): Se buscan los hijos de Y y se seleccionan si son hombres. El orden de los literales en el cuerpo de una regla influye también en la terminación. (Un ejemplo se vio en la sección Recursión a izquierdas del tema anterior). Ejemplo: El siguiente programa de “inversa de una lista” terminará para preguntas en modo (in, out). Sin embargo, en modo (out, in) el árbol de búsqueda tendrá una rama infinita, por lo que tras dar la respuesta correcta se quedaría ciclando. 1. inversa([ ], [ ]). 2. inversa([C|R], Z) :- inversa(R, Y), concatenar(Y, [C], Z). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 50 3. CONTROL en la PROGRAMACIÓN LOGICA Hacemos la pregunta ? inversa(X, [1, 2]). ?inversa(X, [1, 2]). 2. {X→ [C1| R1]} ?inversa(R1, Y1), concatenar(Y1, [C1], [1, 2]). 1. 2. {R1→ [C2| R2]} {R1→ [ ]} ?concatenar([ ], [C1], [1, 2]). ?inversa(R2, Y2), concatenar(Y2, [C2], Y1), concatenar(Y1, [C1], [1, 2]). ⊗ 1. 2. FRACASO {R2→ [ ]} ?concatenar([ ], [C2], Y1), • concatenar(Y1, [C1], [1, 2]). • • {Y1→ [C2]} rama infinita ?concatemar([C2], [C1], [1, 2]). {C2→1} ?concatenar([ ], [C1], [2]). {C1→2} EXITO X=[2, 1] Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 3.2. 51 Control con cortes PROLOG tiene un predicado del sistema, llamado "corte", que afecta al comportamiento procedural de los programas. Su principal función es reducir el espacio de búsqueda de las computaciones PROLOG, "podando" el árbol de forma dinámica. El corte puede usarse: para que el sistema no recorra caminos en el árbol que el programador sabe que no producen soluciones. para cortar caminos que dan soluciones y poder implementar una forma débil de negación. Muchos de los usos de corte sólo pueden ser interpretados proceduralmente, en contraste con el estilo declarativo de la programación lógica "pura". Sin embargo, haciendo un buen uso de él, se mejora la eficiencia de los programas sin comprometer su claridad. 3.2.1. Operador "corte" El corte es, desde el punto de vista sintáctico, un átomo formado por un símbolo de predicado denotado "!" y sin argumentos. Como fin se satisface siempre y no puede re-satisfacerse. Su uso permite podar ramas del árbol de búsqueda de soluciones. Como consecuencia, un programa que use el corte será generalmente más rápido y ocupará menos espacio en memoria (no tiene que recordar los puntos de “backtracking” para una posible reevaluación). Ejemplo: El programa "mezcla (L1, L2, L)", en modo (in,in,out), mezcla dos listas ordenadas dadas L1 e L2 devolviendo la mezcla en la lista ordenada L 1. mezcla ([X| Xs], [Y| Ys], [X| Zs]) :- X<Y, mezcla (Xs, [Y| Ys], Zs). 2. mezcla ([X| Xs], [Y| Ys], [X,Y | Zs]) :- X=Y, mezcla (Xs, Ys, Zs). 3. mezcla ([X| Xs], [Y| Ys], [Y| Zs]) :- X>Y, mezcla ([X| Xs], Ys, Zs). 4. mezcla (Xs, [ ], Xs). 5. mezcla ([ ], Ys, Ys). La mezcla de dos listas ordenadas es una operación determinista. Sólo una de las cinco sentencias "mezcla" se aplica para cada fin (no trivial) en una computación dada. En concreto, cuando comparamos dos números X e Y, sólo uno de los tres tests X<Y, X=Y ó X>Y será cierto. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 52 3. CONTROL en la PROGRAMACIÓN LOGICA El corte puede usarse para expresar la naturaleza mutuamente exclusiva de los tests de las tres primeras sentencias. Se colocará en las sentencias después de los tests: 1. mezcla ([X | Xs], [Y | Ys], [X | Zs]) :- X<Y, !, mezcla (Xs, [Y | Ys], Zs). 2. mezcla ([X| Xs], [Y| Ys], [X, Y| Zs]) :- X=Y, !, mezcla (Xs, Ys, Zs). 3. mezcla ([X| Xs], [Y| Ys], [Y| Zs]) :- X>Y, !, mezcla ([X| Xs], Ys, Zs). 4. mezcla (Xs, [ ], Xs):- !. 5. mezcla ([ ], Ys, Ys). Por otra parte, los dos casos básicos del programa (sentencias 4 y 5) son también deterministas: La sentencia correcta a usar se elige por unificación con la cabeza, por eso el corte aparece como el primer fin (en este caso el único) en el cuerpo de la regla 4. Dicho corte elimina la solución redundante (se volvería a obtener con la regla 5) al objetivo ?mezcla([ ], [ ], X). Siguiendo con el ejemplo de mezcla, en el recuadro siguiente se muestra el árbol de búsqueda de soluciones relativo a la pregunta ?mezcla([1,3,5], [2,3], X) sobre el programa de mezcla con cortes añadidos. La explicación de las “podas” realizadas es la siguiente: • • • • El objetivo principal ?mezcla[1,3,5], [2,3], X) se reduce primero (por la regla 1) al objetivo ? 1<2, !, mezcla([3,5], [2,3], X1). El fin 1<2 se satisface, llegándose a un nodo del árbol cuyo primer fin es el corte. El efecto de ejecutar el corte (paso marcado con (*1)) es podar las ramas marcadas con (a) y (b), y congelar las decisiones tomadas desde la sentencia que produjo el corte hasta la satisfacción del corte (ramas c y d). El corte de la regla 1 ha aparecido en el árbol porque el fin mezcla[1, 3, 5], [2, 3], X) se unificó con la cabeza de la regla 1. A dicho fin se le denomina fin padre del corte introducido. El resto del desarrollo del árbol es semejante, con dos cortes más podando ramas y congelando decisiones. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 53 ?mezcla([1, 3, 5], [2, 3], X). 3. 2. 1. {X→ [1| Zs1]} (c) ?1<2, !, mezcla([3, 5], [2, 3], Zs1). (a) (b) (d) (*1) poda de las ramas (a) y (b), y congelación de las ramas (c) y (d). ?!, mezcla([3, 5], [2, 3], Zs1). (*1) ?mezcla([3, 5], [2, 3], Zs1). (e) 2. 3. ?3=2, !, . . . (f) ?3>2, !, mezcla([3, 5], [3], Zs2). 1. ?3<2, !, . . . FRACASO { Zs1→ [2| Zs2]} (g) ? !, mezcla([3, 5], [3], Zs2). FRACASO (*2) ?mezcla([3, 5], [3], Zs2). 1. 2. ?3<3, !, . . . { Zs2→ [3, 3| Zs3]} 3. (*2) poda de ninguna rama (e) y congelación de las ramas (f) y (g). (h) FRACASO (i) ?3=3, !, mezcla([5], [ ], Zs3). (j) ?!, mezcla([5], [ ], Zs3). (*3) (*3) poda de la rama (h) y congelación de las ramas (i) y (j). ?mezcla([5], [ ], Zs3). 4. ? !. { Zs3→ [5]} Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 54 3. CONTROL en la PROGRAMACIÓN LOGICA Operacionalmente, el efecto del corte es el siguiente: ! se satisface y “congela” todas las elecciones hechas desde que “su fin padre” se unificó con la cabeza de la sentencia que le contiene. Esta definición dada sobre el efecto del corte es completa y precisa, no obstante, se pueden aclarar sus implicaciones: • Primero, un corte poda todas las sentencias debajo de él (viéndolo en el programa). Un fin P unificado con una sentencia que contiene un corte que se ha satisfecho no podrá producir soluciones usando sentencias debajo de aquélla. • Segundo, un corte poda todas las soluciones alternativas de la conjunción de fines que aparezcan a su izquierda en la sentencia. Esto es, una conjunción de fines seguida por un corte producirá como máximo una solución (se congelan las decisiones tomadas desde el fin que provocó el corte hasta que éste se satisface). Por otra parte, el corte no afecta a los fines que estén a su derecha en la sentencia. Estos pueden producir más de una solución, en caso de vuelta atrás (backtracking). Sin embargo, una vez que esta conjunción fracasa, la búsqueda continuará a partir de la última alternativa que había por encima de la elección de la sentencia que contiene el corte. • Resumiendo, de forma general, el efecto de un corte en una regla C de la forma A :- B1, ..., Bk, !, Bk+2, ..., Bn, es el siguiente: • Si el fin actual G se unifica con A y los fines B1, ..., Bk se satisfacen, entonces el programa fija ya la elección de esta regla para reducir • G; cualquier otra regla alternativa para A que pueda unificarse con G se ignora. Además, si los Bi con i>k fracasan, la vuelta atrás sólo puede hacerse hasta el corte. Las demás elecciones que quedaran para computar los Bi con i≤ k se han cortado del árbol de búsqueda. • Si el backtracking llega de hecho al corte entonces éste fracasa y la búsqueda continúa desde la última elección hecha antes de que G eligiera la regla C. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 55 3.2.2. Cortes verdes En el ejemplo anterior, los cortes introducidos no han alterado el significado declarativo del programa. Se llaman "cortes verdes". Generalmente se usan para expresar determinismo: la parte del cuerpo que precede al corte (o a veces el patrón de la cabeza) comprueba un caso que excluye a todos los demás. Ejemplo: El predicado ordenar(L,R) indica que R es el resultado de ordenar la lista L por medio de intercambios sucesivos. Este predicado usará ordenada(L) que nos dice si la lista L está ordenada. 1. ordenar (L, R) :- concatenar(P, [X,Y | S], L), X>Y, !, concatenar(P, [Y,X| S], NL), ordenar(NL, R). 2. ordenar (L, L) :- ordenada(L). 3. concatenar ([ ], L, L). 4. concatenar ([X|Y], L, [X|Z]) :- concatenar(Y, L, Z). {L1→[3,2,1], R1→R} ?ordenar([3, 2, 1], R). 1. 2. (a) ?concatenar(P1, [X1,Y1| S1], [3,2,1]), X1>Y1, !, concatenar(P1, [Y1,X1| S1], NL1), ordenar(NL1, R). 3. 4. {P1→ [ ], X1→3, Y1→2, S1→[1], L2→[3,2,1]} (b) ? 3>2, !, concatenar([ ], [2,3| [1] ], NL1), ordenar(NL1, R). {NL1→ [2,3,1]} 3. ?ordenar([2, 3, 1], R). • • • Apuntes: Curso de PROGRAMACION LOGICA Se realizan varios pasos a la vez. Primero, se satisface la condición 3>2. Segundo, se satisface el corte, por lo que se podan las ramas (a) y (b). Tercero, el objetivo concatenar se unifica con la regla 3. Marisa Navarro 2008-09 56 3. CONTROL en la PROGRAMACIÓN LOGICA Un objetivo como ? ordenar([3, 2, 1], R), se resolverá intercambiando primero 3 con 2 pues es la primera solución que se calcula para el objetivo. Por tanto los primeros pasos de la ordenación son cómo se ven en el fragmento del árbol de búsqueda anterior. Al satisfacerse el corte, se evita la solución redundante que comenzaría intercambiando 2 con 1 y se ha obtenido la ordenación parcial de los dos primeros elementos de la lista. Se deja como ejercicio: continuar con el árbol desarrollando además las llamadas correspondientes al predicado concatenar, ¿qué sucede si el predicado concatenar tiene un corte en su caso trivial? 3.2.3. Cortes rojos Un corte es “rojo” si afecta a la semántica declarativa del programa. En el ejemplo anterior de ordenación de una lista por intercambios sucesivos, el efecto del corte hace que un objetivo se resuelva con la sentencia 2 sólo cuando la lista ya está ordenada, pudiéndose por tanto suprimir el cuerpo de la sentencia 2 (ordenada(L)) sin afectar al comportamiento operacional del programa: 1. ordenar (L, R) :- concatenar(P, [X,Y | S], L), X>Y, !, concatenar(P, [Y,X| S], NL), ordenar(NL, R). 2. ordenar (L, L). Así, este nuevo programa “funciona” igual que el anterior y además evita la comprobación última de ver que la lista está ordenada. Sin embargo, en esta propuesta queda afectada la semántica declarativa del programa: ¡la segunda sentencia (por sí sola) nos dice que toda lista está ordenada! Por tanto, el corte del programa es ahora rojo. Se debe destacar que el orden de las sentencias es ahora esencial. Por otro lado, el cuerpo de la sentencia 2 en la versión anterior sí mantiene la corrección declarativa del programa. Generalmente los cortes rojos aparecen al omitir del programa la comprobación explícita de una condición que se sabe se va a satisfacer en un cierto modo de uso (en nuestro caso el modo de uso sería in para el primer parámetro y out para el segundo). Para otros usos del programa el corte rojo puede provocar incorrecciones no esperadas, por lo que deben utilizarse con cuidado. Veamos a continuación un ejemplo para ilustrar este problema. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 57 Ejemplo: Queremos definir "minimo(X, Y, Z)" para indicar que Z es el mínimo de X e Y. Pensamos en un uso (in, in, out): dados X e Y , calcular el mínimo y devolverlo en Z. Para ello definimos: Versión 1 1. minimo (X, Y, X) :- X<= Y, !. 2. minimo (X, Y, Y). El corte es rojo pues afecta a la semántica declarativa del programa (en la segunda sentencia), pero para usos (in, in, out) la semántica operacional (o funcionamiento del programa) se corresponde con la del programa siguiente (donde el corte es verde al hacerse explícita la condición de la regla 2): Versión 2 1. minimo (X, Y, X) :- X<= Y, !. 2. minimo (X, Y, Y) :- Y<X. Por ejemplo, con ambos programas ante la pregunta ?minimo(7, 3, Z) la contestación es Z=3 Sin embargo, para el modo de uso (in, in, in) el programa de la versión 1 es incorrecto: ? minimo(2, 5, 5) si Esto se debe a que minimo(2, 5, 5) NO se unifica con minimo(X, Y, X) por lo que no puede usarse la sentencia 1. Entonces el sistema intenta con la segunda y, al unificarse minimo(2, 5, 5) con minimo(X, Y, Y) se obtiene la refutación: respuesta si. Con la versión 2 (declarativamente correcta) la misma pregunta obtiene la respuesta esperada: ? minimo(2, 5, 5) no Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 58 3.3 3. CONTROL en la PROGRAMACIÓN LOGICA Distintos usos del corte Veremos a continuación algunos casos estándar en los que se utiliza el corte con un propósito determinado. 3.3.1. Confirmación de la elección de una regla En el ejemplo visto en el apartado anterior (mezcla de dos listas ordenadas) el uso del corte tras una comprobación confirma la elección adecuada de dicha regla para el objetivo dado (permitiendo descartar las demás). Es decir, este uso se da cuando existe determinismo. Aquí vamos a ver otros dos ejemplos de este uso del corte. Ejemplo 1.- Borrar todas las apariciones de un cierto elemento en una lista dada. Usaremos borrar(X, L, LN) para indicar "LN es la lista obtenida al borrar todas las apariciones de X en la lista L". Modo de uso: (in, in, out), es decir, primer y segundo parámetros de entrada y tercer parámetro de salida. borrar(X, [ ], [ ]) :- !. borrar(X, [X|L], LN) :- !, borrar(X, L, LN). borrar(X, [Z|L], [Z|LN) :- borrar(X, L, LN). Ejemplo 2.- Comprobar si una lista es sublista de otra. Usaremos sublista(X, Y) para indicar "X es sublista de Y". Modo de uso (in, in). sublista([ ], L) :- !. sublista([X|Y], [X|M]) :- prefijo(Y, M), !. sublista(L, [X|M]) :- sublista(L, M). prefijo([ ], L). prefijo([X|Y], [X|Z]) :- prefijo(Y, Z). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 59 3.3.2. Combinación corte-fallo En PROLOG existe un predicado predefinido fail que siempre fracasa. La combinación del predicado corte con fail, permite descartar opciones tras haberse comprobado una o varias condiciones. Puede ser útil al programar procedimientos de los que se conoce precisamente las condiciones por las que, si se cumplen, dicho procedimiento falla. Ejemplo: El siguiente procedimiento pretende definir cuándo una persona es cura católico. Está claro que existen condiciones que a priori determinan que una persona no será cura católico: si es mujer, si esta casado, etc. Modo de uso: (in) 1. 2. 3. 4. cura-catolico(X):- mujer(X), !, fail. cura-catolico(X):- casado(X), !, fail. cura-catolico(X):- edad(X, N), N<25, !, fail. cura-catolico(X):- votos(X). El orden de las sentencias es esencial para el buen funcionamiento del programa, ya que se pueden dar simultáneamente condiciones de varias reglas. Por ejemplo, una persona puede haber hecho los votos pero estar casada, entonces ¿será cura católico? Para nuestro programa la solución está clara y su respuesta será negativa. Sin embargo, si invertimos el orden de las reglas, 2 y 4, la respuesta se hace afirmativa. Notar además que el programa no funciona para modo de uso (out). 3.3.3. Generación y prueba Se usa también el corte para acabar una secuencia de generación y prueba en programas con la siguiente estructura: 1.- Una serie de fines generan posibles soluciones vía “backtracking”. 2.- Otros fines comprueban si dichas soluciones son las apropiadas. 3.- Se obtiene una solución generada que verifique las pruebas. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 60 3. CONTROL en la PROGRAMACIÓN LOGICA Ejemplo: Ordenación de una lista buscando una permutación ordenada de la lista dada. Modo de uso: (in, out). Suponemos que permutar(X,Y) obtiene en Y una permutación de X. Usar este fin como generador significa que al hacer una llamada como ?permutar(dato, Z) se obtienen distintas soluciones vía “backtracking” en Z. ordenar(X,Y) :- permutar(X,Y), ordenada(Y), !. (1) generador (2) prueba (3) obtención • • • 3.4. El fin generador (1), permutar, obtiene una permutación de X en Y que pasa al fin prueba para comprobar si Y está ordenada. El fin prueba (2), ordenada, mira si la lista está ordenada. Si no lo está la vuelta atrás se encargará de re-satisfacer el primer fin buscando una nueva permutación. Si lo está, es la solución buscada y se da como respuesta. El corte impide la vuelta atrás (3), por lo que no se generarán más permutaciones. La negación como fallo finito La combinación corte-fallo vista en la sección anterior, puede usarse para implementar una forma débil de negación en PROLOG, mediante el meta-predicado not definido: 1. 2. not(X) :- X, !, fail. not(X). En not(X) se usa una meta-variable X que se unificará con un átomo, no con un término. El comportamiento de este programa, al contestar una pregunta del tipo ?not(G) donde G es un fin, es el siguiente: Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 3. CONTROL en la PROGRAMACIÓN LOGICA 61 ?not(G). 1. 2. ?G, !, fail. El fin not(G) se unifica con la primera regla del programa (ligando X con el fin G) y produce el objetivo ?G, ! ,fail. ?not(G). 1. Si G se satisface entonces se encuentra el corte ! que poda la segunda rama del árbol y "fail" hace que el fin not(G) fracase. 2. ?G, !, fail. ?!, fail. FRACASO ?not(G). Si G fracasa entonces se usa la segunda regla y el fin not(G) se satisface 1. 2. ?G, !, fail. FRACASO EXITO La terminación del cómputo para una pregunta ?not(G) depende de la terminación del cómputo para ?G. Si éste no termina, entonces el primero puede terminar o no, según que en el árbol de búsqueda para ?G se encuentre una refutación antes que una rama infinita. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 62 3. CONTROL en la PROGRAMACIÓN LOGICA Cabe señalar que el uso de la negación en PROLOG no se corresponde con la negación lógica. Por ello, es importante indicar que una pregunta negada que contenga variables, no tendrá como respuesta valores para dichas variables. Es decir, ?not(G) siempre tendrá como respuesta un si (éxito) o un no (fracaso), según corresponda, aun cuando G sea un átomo con variables. Además la respuesta, en dicho caso, puede ser incorrecta, como se muestra en el siguiente ejemplo. Ejemplo: Dado el programa P = {p(a). q(b).} y dado el objetivo con variables G = p(X) se tiene: ? not(p(X)) no debido a que Prolog pregunta por ?p(X) y éste se satisface para X = a. Es decir, el objetivo G = p(X), que se corresponde con la fórmula ∃X p(X), se ha hecho cierto. Por tanto, la negación de esta fórmula ¬∃X p(X) equivalente a ∀X¬ ¬p(X) . ¬p(X) ha de ser falsa. La respuesta ”no” es correcta si se corresponde con preguntar por ∀X¬ Sin embargo, la interpretación correcta de la pregunta ?not(p(X)) , haciendo explícito el cuantificador, es la fórmula ∃X ¬p(X) que, respecto al programa P, debería dar como respuesta X = b. El problema es que ∀X¬ ¬p(X) y ∃X ¬p(X) son fórmulas no equivalentes. Para evitar esta incorrección, se debe asegurar el uso de la negación en PROLOG sólo sobre átomos de base (sin variables) ya que en dicho caso ∀X¬ ¬A = ∃X¬ ¬A = ¬A Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 63 Tema 4. OTROS ASPECTOS de PROLOG Apuntes: Curso de PROGRAMACION LOGICA 4.1. Aritmética en PROLOG 4.2. Predicados metalógicos 4.3. Entrada / Salida Marisa Navarro 2008-09 64 Apuntes: Curso de PROGRAMACION LOGICA 4. OTROS ASPECTOS de PROLOG Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 65 TEMA 4. OTROS ASPECTOS de PROLOG Este tema se divide en tres secciones que estudian predicados predefinidos en PROLOG: predicados aritméticos, predicados de manipulación de la base de conocimiento y predicados para la Entrada/Salida. Todos ellos son usuales en los distintos sistemas Prolog que existen en el mercado, aunque podrían variar ligeramente de un sistema a otro. 4.1 Aritmética en PROLOG En PROLOG la aritmética se realiza con ciertos predicados predefinidos que toman como argumento una expresión aritmética (trabajando sobre enteros y reales) y la evalúa. Expresiones aritméticas Una expresión aritmética es un término construido con números, variables y funtores que representan funciones aritméticas. Sólo se permiten ciertos funtores en estas expresiones. Algunos de ellos son los siguientes: E1+ E2 suma E1 - E2 E1* E2 E1 / E2 E1 // E2 diferencia producto cociente cociente entero -E E1 mod E2 opuesto módulo Una expresión aritmética sólo puede ser evaluada si no contiene variables libres. En otro caso aparece un error de evaluación Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 66 4. OTROS ASPECTOS de PROLOG Predicados aritméticos evaluables Las expresiones aritméticas sólo son evaluadas cuando se pasan como argumentos a predicados aritméticos evaluables: X is E Evalúa E y unifica el resultado con X. Ejemplos: ? X is 4/2 + 3/7. X = 2.42857 ? X is 2*4, 2 is X//3. X=8 ? X is 3, Y is X+4. X = 3, Y = 7 ? Y is X+4, X is 3. ERROR ? 3+4 is 3+4. no Notas: 1. "X is X+1" da FRACASO si X está instanciada en la llamada y ERROR aritmético si X está libre. 2. El orden de los literales es relevante en el uso de predicados evaluables. En el cuarto ejemplo se tiene ERROR porque X está libre. Comparación aritmética Los siguientes predicados evalúan sus dos argumentos como expresiones aritméticas y comparan los resultados. E1 op E2 Se evalúan las expresiones E1 y E2 y se comparan según los operadores siguientes: E1 > E2 E1 >= E2 E1 < E2 E1 =< E2 E1 =:= E2 mayor mayor o igual E1 =\= E2 distinto Apuntes: Curso de PROGRAMACION LOGICA menor menor o igual igual Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 4.2 67 Predicados metalógicos A continuación se muestran algunos predicados meta-lógicos usuales en los sistemas PROLOG. Manipulación de términos Los siguientes predicados meta-lógicos se usan para examinar el estado de instanciación actual de un término: var(Term) nonvar(Term) ground(Term) Es cierto si Term es una variable libre (no instanciada). Es cierto si Term no es una variable libre. Es cierto si Term no contiene variables libres. atom(Term) atomic(Term) Es cierto si Term está instanciado a un identificador Prolog [Nota: no confundir con átomo lógico]. Es cierto si Term está instanciado a un identificador ó a un número. number(Term) integer(Term) float(Term) Es cierto si Term está instanciado a un número (entero o real). Es cierto si Term está instanciado a un entero. Es cierto si Term está instanciado a un real. compound(Term) is_list(Term) Es cierto si Term está instanciado a una estructura (es decir, a un término compuesto). Es cierto si Term está instanciado a una lista (es decir, a la lista vacía [] ó a un término con funtor ‘.’ y aridad 2, donde el segundo argumento es una lista. Este predicado actúa como si estuviera definido de la siguiente forma: is_list(X) :- var(X), !, fail. is_list([]). is_list([_|T]) :- is_list(T). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 68 4. OTROS ASPECTOS de PROLOG Igualdad de términos La igualdad de términos puede determinarse de diferentes formas. La diferencia clave estriba en si tiene lugar o no la unificación de variables en los términos. El operador “=” es la propia unificación. Esto es, se unifican las variables de los términos que se comparan. Sin embargo, el operador “==” no unifica las variables de los términos que se comparan. Por tanto, una variable (no ligada) sólo será igual a sí misma. T1 = T2 T1 \= T2 T1 == T2 T1 \== T2 Es cierto si T1 y T2 pueden unificarse. Es cierto si T1 y T2 no pueden unificarse. Es cierto si T1 y T2 son idénticos, es decir, se unifican sin que haya ligaduras de variables. Es cierto si T1 y T2 no son idénticos. Ejemplos: ? a = X. X=a si ? X = Y. ? a == a. X = _G3, Y = _G3 si si ? a == X. no ? X == Y. no ? X == X. X = _G2 si Acceso a estructuras a) Conversión de una estructura a lista (y viceversa): E =.. L L es la lista formada por el funtor y los componentes de la estructura E. (un átomo se comporta como un funtor con 0 componentes) Ejemplos: ? libro(autor, título) =.. L. L = [libro, autor, título] Apuntes: Curso de PROGRAMACION LOGICA ? E =.. [libro, autor, título]. E = libro(autor, título) ? libro =.. L. L = [libro] ? E =.. [libro]. E = libro Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 69 b) Acceso al constructor y a los componentes: functor(E, F, A) La estructura E tiene funtor F y aridad A Dos modos de uso de este predicado: (in, out, out) y (out, in, in) Ejemplos: ? functor(libro(autor, titulo), N, A). N = libro, A = 2 ? functor(X, libro, 2). X = libro(_G358, _G359) ? functor(p, N, A). N = p, A = 0 ? functor(X, p, 0). X=p arg(P, E, C) La estructura E tiene la componente C en la posición P (contando desde 1). Modos de uso: (in, in, out) y (out,in,in) Ejemplos: ? arg(2, libro(autor, titulo), X). X = titulo ? arg(N, libro(autor, titulo), autor). N=1 Gestión de la Base de Cláusulas Veremos a continuación los principales predicados del sistema PROLOG para gestionar la Base de Cláusulas (BC). Es decir, meta-predicados de consulta, inserción, eliminación, ... de las sentencias (o cláusulas) de programa cargadas en memoria. Para ilustrar cómo funcionan dichos predicados, veremos ejemplos de ellos sobre la siguiente BC: :- dynamic padre/2, hijo_a/2, abuelo/2. padre(jon, carlos). padre(carlos, maria). hijo_a(X,Y) :- padre(Y, X). abuelo(X, Z) :- padre(X, Y), padre(Y, Z). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 70 4. OTROS ASPECTOS de PROLOG NOTA: dynamic +Name/+Arity, ... informa al intérprete que la definición del(os) predicado(s) puede cambiar durante la ejecución (usando assert y/o retract). En otro caso se consideran estáticos, sin permitir cambios durante la ejecución. • Predicado para consultar la BC: clause(Cabeza,Cuerpo) unifica Cabeza (que no debe ser una variable libre) y Cuerpo con la cabeza y el cuerpo (respectivamente) de una cláusula de la BC. Ejemplos: ? clause(padre(X,Y),Z). X = jon, Y = carlos, Z = true ; X = carlos, Y = maria, Z = true ? clause(hijo_a(X,Y),Z). ? clause(abuelo(X,Z),U). X =_G1, Y =_G2, Z = padre(_G2,_G1) X=_G1, Z=_G2, U=padre(_G1,_G3) ‘,’ padre(_G3,_G2) • Predicados para insertar cláusulas en la BC: assert(Clausula) ó assertz(Clausula) inserta Clausula (que debe estar instanciada) en la BC al final de la lista de su procedimiento. Ejemplo: ? assert((hijo_a(X,Y) :- madre(Y,X))). Efecto: inserta dicha cláusula como la última del procedimiento "hijo_a". asserta(Clausula) inserta Clausula (que debe estar instanciada a una cláusula) en la BC como la primera de su procedimiento. Ejemplo: ? asserta(padre(juan,eva)). Efecto: inserta dicha cláusula delante de las dos del procedimiento "padre". Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 71 • Predicados para eliminar cláusulas de la BC: retract(Clausula) elimina de la BC la primera cláusula unificable con Clausula (que no debe ser una variable libre). Ejemplo: ? retract(padre(carlos,Y)) Y = maria Efecto: elimina "padre(carlos,maria)" de la BC. abolish(Nombre / Aridad) elimina de la BC todas las cláusulas del procedimiento de nombre Nombre y aridad Aridad. Ejemplo: ? abolish(hijo_a / 2). si Efecto: elimina de la BC todas las cláusulas del predicado "hijo_a" de aridad 2. • Predicados para listar las cláusulas de la BC: listing. Lista todas las cláusulas de la BC. listing(Nombre). Lista todas las que definen al predicado de nombre Nombre listing(Nombre / Aridad). Lista todas las que definen al predicado de nombre Nombre y aridad Aridad NOTA: Usad estos predicados para comprobar cómo queda la BC tras hacer assert , retract y abolish en los ejemplos anteriores. Los predicados de añadir o eliminar cláusulas permiten programar con efectos laterales, por lo que pueden ser peligrosos. Conviene usarlos de forma "legítima", por ejemplo: 1.- Añadiendo hechos que se deduzcan del programa (lemas). Ejemplo: lema(P) :- P, asserta((P :- !)). P es una metavariable. El corte impide que se llame a otro procedimiento para resolver P. Sólo es legítimo si P es determinista. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 72 4. OTROS ASPECTOS de PROLOG 2.- Simulación de variables globales. Ejemplo: asig_var(Nombre,Valor) :- nonvar(Nombre), retract(valor(Nombre,Valor1)),!, asserta(valor(Nombre,Valor)). asig_var(Nombre,Valor) :- nonvar(Nombre), asserta(valor(Nombre,Valor)). Supongamos que representamos nuestras variables globales como términos v(N). Veamos la siguiente sesión: ? asig_var(v(5),10). si Efecto: inserta en la BC la cláusula "valor(v(5),10)". ? valor(v(5),X). X = 10 ? valor(v(N),X), Y is X+1, asig_var(v(N),Y), valor(v(N),Z). N = 5, X = 10, Y = 11, Z =11 Efecto: elimina de la BC la cláusula "valor(v(5),10)" e inserta "valor(v(5),11)". Predicados usuales en PROLOG para recolección de soluciones findall(Instance, Goal, List) List se unifica con la lista de todas las instancias de Instance que hacen cierto a Goal. Si Goal no es cierto para ningún valor de Instance, entonces List se unifica con la lista vacía []. Ejemplos: a(1). a(2). fact(bird, duck). fact(sound, quack). a(3). fact(color, white). ?- findall(X, a(X), L). ?- findall(Nombre:Valor, fact(Nombre, Valor), Lista). L = [1, 2, 3] Lista = [bird:duck, sound:quack, color:white] Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 73 Similar a findall excepto en cómo trata las variables que aparecen en Goal y no en Instance (conocidas como variables libres). Bagof hace backtracking y produce una lista List para cada posible ligadura de las variables libres. Se puede convertir una variable libre a no-libre usando ^ . Si Goal no es cierto para ningún valor de Instance, entonces List se unifica con la lista vacía []. bagof(Instance, Goal, List) Ejemplo: pide(fred, cerveza). pide(tom, vino). pide(jane, cerveza). pide(jane, cola). ?- bagof(P, pide(X, P), L). X = fred L = [cerveza] ; X = tom L = [vino]; X = jane L = [cerveza, cola] %% X es libre, por lo que se hará backtracking. ?- bagof(P, X ^ pide(X, P), L). %% X es no-libre. L = [cerveza, vino, cerveza, cola]. Similar a bagof salvo en que List está ordenada (según el orden estándar) y sin repetidos. setof(Instance, Goal, List) ?- setof(P, X ^ pide(X, P), L). L = [cerveza, cola, vino]. Apuntes: Curso de PROGRAMACION LOGICA %% X es no-libre. Marisa Navarro 2008-09 74 4.3 4. OTROS ASPECTOS de PROLOG Entrada / Salida Los sistemas PROLOG poseen predicados predefinidos para la entrada y salida. Se verán aquí ejemplos de algunos de ellos. • Predicados para la E / S de términos (sobre el “stream” por defecto): read(X) write(T) display(T) lee un término y lo unifica con X (el término debe terminar en punto). escribe el término T (en particular, write(‘texto’) para escribir texto). escribe el término T (sin expandir los operadores) Ejemplos: ?- read(U). | p(1,2). %Introducido por usuario U = p(1, 2) ?- write(7+4). ?- display(7+4). 7+ 4 +(7, 4) ?- read(U), X is U*2, write('el número es '), write(X). | 6. %Introducido por el usuario el número es 12 U=6 X = 12 Para especificar la lectura y escritura de términos sobre un “stream” identificado con ID se usará: read(ID,X) y write(ID,X) • Predicados para la E / S de caracteres (sobre el “stream” por defecto): get0(X) get(X) skip(Char) put(Char) lee un carácter y unifica su código con X. análogo, pero salta los caracteres no imprimibles (por ejemplo, los blancos) lee hasta encontrar el carácter Char. Una llamada a get0(X) detrás leerá el siguiente carácter a Char. escribe el carácter Char (o lo ejecuta si no es imprimible). nl tab(N) produce una nueva línea. escribe N espacios en blanco. Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 75 Equivalentemente sobre un “stream” identificado con ID: get0(ID,X) , get(ID, X) , skip(ID,Char) , put(ID,Char) , nl(ID) y tab(ID,N) NOTA: Char puede ser también el código de un carácter en skip y put. Por ejemplo: put(a) ó put(97) escriben ambos el carácter a. Ejemplos: ?- get0(U). | a % Introducido por el usuario U = 97 • ?- put(a), nl, put(b). a b ? skip(a), get0(T), put(T). | olas % Introducido por el usuario s T = 115 Predicados para construir y analizar átomos: name(Constante, LisCod) establece la relación entre Constante (que debe cumplir atom ó integer) y LisCod consistente en la lista atom_codes(Atomo, LisCod) de códigos (o string equivalente) de los caracteres que conforman Constante. similar al anterior pero Atomo debe cumplir atom (no integer) Ejemplos: ?- name(hola, T). T = [104, 111, 108, 97] • ?- name(C, "hola"). C = hola ?- "hola" == [104,111,108,97] si ?- name(N,"23"), Y is N+5. N = 23 Y = 28 Predicados para abrir y cerrar ficheros: open(file, mode, ID) abre el fichero de nombre file, en modo mode (read, write, append,...), con identificador (var) ID open(file, mode, ID, options) close(id) Apuntes: Curso de PROGRAMACION LOGICA similar al anterior pero describiendo una lista de opciones(alias, type,...) cierra el “stream” de identificador id Marisa Navarro 2008-09 76 • 4. OTROS ASPECTOS de PROLOG Predicados para modificar los dispositivos de E /S : see(fich) hace que fich sea el fichero actual de entrada seeing(F) indica en F cuál es el dispositivo actual de entrada. seen tell(fich) telling(F) told cierra el dispositivo actual de entrada (que volverá a ser user_input) hace que fich sea el fichero actual de salida indica en F cuál es el dispositivo actual de salida. cierra el dispositivo actual de salida (que volverá a ser user_output) EJEMPLO: Se muestra el uso de algunos predicados de E / S en el siguiente programa que lee frases introducidas por el usuario sobre el terminal. Programa: % Programa que lee frases (tecleadas en el terminal). La llamada principal es leer(X). % LEER, lee una frase. % Modo de uso: leer(out frase). leer([P|Ps]) :- get0(C), leepalabra(C,P,C1), restofrase(P,C1,Ps). % RESTOFRASE, dada una palabra y el carácter que la sigue, devuelve el resto de la frase. % Modo de uso: restofrase(in palabra, in caracter, out frase) restofrase(P, _ , []) :- ultimapalabra(P), !. restofrase(P, C, [P1|Ps]) :- leepalabra(C,P1,C1), restofrase(P1,C1,Ps). Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 4. OTROS ASPECTOS de PROLOG 77 % LEEPALABRA, dado un carácter inicial, devuelve una palabra y el carácter que viene detrás de la palabra. % Modo de uso: leepalabra(in carácter, out palabra, out carácter). leepalabra(C,P,C1) :- caracter_unico(C), !, name(P,[C]), get0(C1). leepalabra(C,P,C2) :- en_palabra(C,NuevoC), !, get0(C1), restopalabra(C1,Cs,C2), name(P, [NuevoC | Cs]). leepalabra(C,P,C2) :- get0(C1), leepalabra(C1,P,C2). % RESTOPALABRA, dado un carácter inicial, devuelve la lista de caracteres del resto de la palabra % y el carácter que viene detrás de la palabra. % Modo de uso: leepalabra(in carácter, out lista_de_caracteres, out caracter). restopalabra(C, [NuevoC | Cs], C2) :- en_palabra(C,NuevoC), !, get0(C1), restopalabra(C1,Cs,C2). restopalabra(C,[ ],C). % CARACTER_UNICO, los siguientes caracteres forman palabra por si mismos. % Modo de uso: caracter_unico(in código_caracter). caracter_unico(44). % , caracter_unico(59). caracter_unico(58). caracter_unico(63). caracter_unico(33). % % % % caracter_unico(46). % . ; : ? ! Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09 78 4. OTROS ASPECTOS de PROLOG % ULTIMAPALABRA, las siguientes palabras terminan una frase. % Modo de uso: ultimapalabra(in palabra). ultimapalabra('.'). ultimapalabra('!'). ultimapalabra('?'). % EN_PALABRA, trata los caracteres que pueden aparecer dentro de una palabra. % La segunda cláusula convierte mayúsculas a minúsculas. % Modo de uso: en_palabra(in caracter, out caracter). en_palabra(C,C) :- C>96, C<123. en_palabra(C,L) :- C>64, C<91, L is C+32. % a b ... z % A B ... Z en_palabra(C,C) :- C>47, C<58. en_palabra(39,39). en_palabra(45,45). % 0 1 2 ... 9 % ' % - Ejemplo de ejecución: ?- leer(X), length(X,N). |: Esta es una prueba, contaremos |: las palabras luego. X = [esta, es, una, prueba, (','), contaremos, las, palabras, luego, '.'] N = 10 Apuntes: Curso de PROGRAMACION LOGICA Marisa Navarro 2008-09