Curso avanzado de Prolog

Una de las caracter�sticas m�s �til de Prolog es la posibilidad de a�adir y eliminar cl�usulas de los predicados presentes en el programa, y hacerlo en tiempo de ejecuci�n. Los predicados con esta posibilidad se denominan predicados din�micos.

.�Declaraci�n de predicados din�micos

Estos predicados deben ser previamente marcados mediante la directiva dynamic/1, indicando el nombre del predicado din�mico. El siguiente ejemplo declara el predicado prueba/1 como din�mico. Por lo dem�s, un predicado din�mico es como otro cualquiera excepto en que puede no tener cl�usulas. En ese caso, una llamada al predicado simplemente falla, pero no provoca un error de predicado indefinido.

 :- dynamic prueba/1.

 prueba(abc).
 prueba(123).
 

.�A�adiendo cl�usulas

Para insertar cl�usulas de un predicado din�mico existe una familia de predicados ISO-standard, la familia assert, consistente en los siguientes predicados:

asserta/1 Inserta una nueva cl�usula como si se hubiera escrito al principio del programa.
assertz/1 Inserta una nueva cl�usula como si se hubiera escrito al final del programa.
assert/1 Id�ntico a asserta/1.

La diferencia entre insertar las cl�usulas por el principio o por el final es muy importante puesto que determina el orden de sucesi�n de las soluciones.

El argumento que toman estos predicados es la nueva cl�usula a insertar, pero teniendo en cuenta lo siguiente:

  • Las variables ligadas dentro de la cl�usula se sustituyen por su valor en el momento de insertar dicha cl�usula. Esto es lo l�gico y deseable.
  • Las variables libres dentro de la cl�usula se sustituyen por variables nuevas en el momento de la inserci�n. Es decir, posteriores unificaciones no afectan a la cl�usula ya insertada.
  • Si existen puntos de elecci�n para el predicado modificado (aquel para el que se inserta una nueva cl�usula), estos se mantienen y no se generan nuevos puntos de elecci�n. Es decir, la nueva cl�usula no se tendr� en cuenta hasta que se ejecute un nuevo objetivo para el predicado en cuesti�n.

Como ejemplo veamos como insertar cl�usulas en el predicado prueba/1. Antes recordemos las cl�usulas que ya existen, en orden:

 prueba(abc).
 prueba(123).
 
Ahora a�adimos una cl�usula ejecutando asserta(prueba(666)). El programa queda como sigue:
 prueba(666).
 prueba(abc).
 prueba(123).
 
De nuevo a�adimos una cl�usula ejecutando J=999,assertz(prueba(J)). El programa queda como sigue:
 prueba(666).
 prueba(abc).
 prueba(123).
 prueba(999).
 
Y para finalizar generamos una cl�usula algo mas compleja mediante assertz( (prueba(X) :- X>1024) ) ...
 prueba(666).
 prueba(abc).
 prueba(123).
 prueba(999).
 prueba(H) :-
   H > 1024.
 

.�Eliminando cl�usulas

An�logamente, es posible eliminar cl�usulas de predicados din�micos mediante la familia de predicados retract consistente en:

retract/1 Elimina �nicamente la primera cl�usula que unifique con el argumento. Siempre se elimina por el principio del programa.
rectractall/1 Elimina todas las cl�usulas que unifiquen con el argumento.

De nuevo, hay que considerar que:

  • Los puntos de elecci�n que ya existieren a causa de las cl�usulas eliminadas permanecen mientras sea necesario.
  • retract/1 es constructivo. Las variables libres se ligan a los elementos de la cl�usula eliminada.
  • retract/1 tiene tantas soluciones como cl�usulas existan que unifiquen con el argumento. Es decir, si se hace backtracking sobre �l, se eliminan todas las cl�usulas que unifiquen. Falla cuando no hay m�s cl�usulas a unificar.
  • retractall/1 solamente tiene una solucion, y no es constructivo. No liga las variables libres.

En el siguiente ejemplo se observa el funcionamiento de assert y retract.

?- asserta(ejemplo(i(k))).

yes
?- asserta(ejemplo(i(l))).

yes
?- asserta(ejemplo(j(X))).

yes
?- assertz(ejemplo(j(2))).

yes
?- assertz(ejemplo(j(8))).

yes
?- ejemplo(X),display(ejemplo(X)),nl,fail.
ejemplo(j(_510))
ejemplo(i(l))
ejemplo(i(k))
ejemplo(j(2))
ejemplo(j(8))

no
?- retractall(ejemplo(i(X))).

yes
?- ejemplo(X),display(ejemplo(X)),nl,fail.
ejemplo(j(_510))
ejemplo(j(2))
ejemplo(j(8))

%% -- Nota --
%% Aqui provocamos el backtracking pidiendo
%% otra solucion.

no
?- retract(ejemplo(j(X))).

yes
?- retract(ejemplo(j(X))).

X = 2 ? ;

X = 8 ? ;

no
?- 

.�Finalidad de los predicados din�micos

El lector se preguntar� qu� finalidad tiene la propiedad tan espantosa de modificar el c�digo. Haciendo un mal uso de los predicados din�micos solamente provocamos ilegibilidad del programa, dificultad para realizar trazas, efectos laterales y otras desgracias. El �nico uso leg�timo es la implementaci�n de estado en los programas Prolog. Supongamos que necesitamos un programa que controle la temperatura de una sala:

 :- dynamic temperatura/1.
 temperatura(23). 
  
 sensor_temperatura :- 
    esperar(10), 
    leer_temperatura(NuevaTemperatura), 
    rectract(temperatura(_AnteriorTemperatura)),
    assert(temperatura(NuevaTemperatura)),
    !, 
    sensor_temperatura. 
 

Sin assert/retract no ser�a posible almacenar el estado del sensor de temperatura. En general, solamente deber�an ser predicados din�micos aquellos que almacenan hechos simples, es decir, aquellos cuyas cl�usulas no tienen cuerpo.

.�Ejemplo

El siguiente ejemplo implementa una pila de datos al estilo imperativo mediante assert/retract.

 :- module(pila,[],[]).

 :- export(push/1).
 :- export(pop/1).

 :- dynamic datos/1.

 push(NuevoDato) :-
    asserta(datos(NuevoDato)).

 pop(Dato) :-
    retract(datos(Dato)),
    !.
 

Para implementar una cola en lugar de una pila bastar�a con sustituir asserta por assertz.

.�Nota sobre la coherencia l�gica de los programas

Ya hemos mencionado anteriormente el efecto de los predicados assert/retract sobre los puntos de elecci�n en el programa, sin embargo, vamos a insistir un poco m�s sobre esto.

Decimos que un programa tiene coherencia l�gica cuando hace exactamente lo que dice el programa escrito. Pero los predicados din�micos podr�an, en principio, provocar comportamientos inexplicables en un programa. Veamos un ejemplo, el siguiente programa consiste en un bucle de fallo. Lo que dice el programa es que se hace backtracking sobre el predicado test/1, que tiene un n�mero finito de soluciones. Por tanto es un programa finito. Hemos numerado las lineas del programa que nos interesan.

 test(sol1).
 test(sol2).

 main :-
   test(X),                   %% Linea 1
   display(X), nl,            %% Linea 2
   asserta(test(sol)),        %% Linea 3
   fail.                      %% Linea 4
 

Ahora supongamos que asserta/1 no respetase los puntos de elecci�n. Ocurrir�a lo siguiente:

  • LLegamos al objetivo test(X) en la l�nea 1. Se insertan dos puntos de elecci�n.
  • Se elimina el punto de elecci�n 1 y ejecuta la linea 2.
  • La l�nea 3 inserta un tercer punto de elecci�n.
  • La l�nea 4 provoca el backtracking.
  • El backtracking se para en la linea 1, eliminando el punto de elecci�n 2.
  • La linea 3 inserta un cuarto punto de elecci�n.
  • Se repite de nuevo el backtracking, pero ahora hay un tercer punto de elecci�n, luego se repite otra vez todo el proceso.

El programa resulta infinito porque asserta/1 no para de insertar puntos de elecci�n. Pero eso no es lo que dice el programa.

Afortunadamente, assert/retract no modifican los puntos de elecci�n hasta que no se han eliminado todos los que existieran previamente del predicado afectado. Por eso, la ejecuci�n de main/1 llevar�a al siguiente resultado:

 ?- main.
 sol1
 sol2

 yes
 ?-
 

Solamente, al ejecutar main/1 por segunda vez encontramos un resultado distinto:

 ?- main.
 sol
 sol1
 sol2

 yes
 ?-
 

Esto es lo que se denomina mantener la coherencia l�gica del programa. Es decir, no se permite que un programa se modifique a s� mismo hasta que no termine de ejecutarse la parte afectada.

� Copyright 2002

Angel Fern�ndez Pineda.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO