Categorías destacadas
programacion php    
Artículo
13
¡votar!

 Curso avanzado de Prolog


Predicados dinámicos

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.

Publicado por:
manuel delgado
Recomendar
a un amigo
Compartir
en redes
 
Comentarios
Walther Degreiff dice:

SUPER!

22/05/2010, a las 17:04:14
 
BBDD
Entornos de desarrollo
Entretenimiento
Herramientas
Internet
Lenguajes de script
Lenguajes imperativos
Lenguajes orientados a objeto
Otros lenguajes
Plataformas
Teoría
Varios
Copyright © 1998-2011 Programación en Castellano. Todos los derechos reservados
Datos legales | Politica de privacidad | Contacte con nosotros | Publicidad

Diseño web y desarrollo web. Un proyecto de los hermanos Carrero.

Red internet:
Juegos gratis | Servidores dedicados
Más internet: Password | Directorio de weblogs | Favicon