1. TUTORIAL DE PROLOG
( Ver 3.47)
Índice
INTRODUCCIÓN.
CAPÍTULO 1. COMO CORRER PROLOG.
1.1 Comienzo.
1.2 Lectura de Archivos con Programas.
1.3 Ingreso de Cláusulas a la Terminal.
1.4 Directivas: Preguntas y Comandos.
1.5 Errores de Sintaxis.
1.6 Predicados no definidos.
1.7 Ejecución de Programas e Interrupción de éstos.
1.8 Salida del Intérprete.
1.9 Ejecuciones Anidadas - Break y Abort.
1.10 Guardando y Recuperando Estados de Programas.
1.11 Inicialización.
1.12 Entradas a Prolog.
CAPÍTULO 2. DEPURACIÓN.
2.1 El Modelo de Flujo del Procedimiento Box Control.
2.2 Predicados Básicos de Depuración .
2.3 Rastreo.
2.4 Spy-Points.
2.5 Formato de los Mensajes de Depuración.
2.6 Opciones Disponibles Durante la Depuración.
2.7 Consultar Durante la Depuración.
2. CAPÍTULO 3. COMPILACIÓN.
3.1 Llamando al Compilador
3.2 Declaraciones Públicas.
3.3 Mezclando Código Compilado y Código Interpretado.
3.4 Declaraciones de Modo.
3.5 Indexación.
3.6 Optimización de la Cola de Recursión.
3.7 Limitaciones Prácticas.
CAPÍTULO 4. CONSTRUCCION DE PROCEDIMIENTOS.
4.1 Entradas / Salidas.
4.1.1 Lectura de Programas.
4.1.2 Manejo de Archivos.
4.1.2.1 Un Ejemplo.
4.1.3 Entrada y Salida de Términos.
4.1.4 Entrada / Salida de Caracteres.
4.2 Aritmética.
4.3 Comparación de Términos.
4.4 Ventajas.
4.5 Controles Extras.
4.6 Información Acerca del Estado de los Programas.
4.7 Meta-lógica.
4.8 Modificación de los Programas.
4.9 Base de Datos Interna.
4.10 Sets.
4.11 Compilando Programas.
4.12 Depurando.
4.13 Clausulas de Gramática.
4.14 Ambiente.
3. INTRODUCCIÓN.
Prolog es un lenguaje de programación simple pero poderoso desarrollado en la
Universidad de Marsella como una herramienta práctica para programación lógica.
Desde el punto de vista del usuario, la ventaja principal es la facilidad para
programar, ya que se pueden escribir rápidamente y con pocos errores, programas
claramente leíbles.
Para una introducción al Prolog, se recomienda que se consulte el libro de Closkin y
Mellish (81), sin embargo, para beneficio de aquellos que no tiene acceso al libro y
para aquellos que tienen conocimientos anteriores de lógica, se encuentra un sumario
del lenguaje en el apéndice I de esta obra.
Este manual describe el Sistema Prolog que fue desarrollado en el Departamento de
Inteligencia Artificial de la Universidad de Edimburgo expresamente para el
DECsystem-10. Este sistema se compone de un intérprete y un compilador, ambos
escritos también en Prolog. A nivel del usuario, el compilador se puede considerar
como un procedimiento integrado en el sistema que puede ser llamado por el
intérprete.
Ya compilado, un procedimiento se puede correr a una velocidad de 10 a 20 veces más
rápida, así como su almacenamiento también se ve reducido. Sin embargo, se
recomienda que los usuarios nuevos ganen experiencia con el intérprete antes de
intenten usar el compilador. El intérprete facilita el desarrollo y la prueba de
programas así como también provee facilidades muy poderosas para la depuración
del código.
Ciertos aspectos del Prolog no se han previsto en la instalación, por ejemplo el tipo de
monitor en el que se va a usar, aspecto que lleva muchos de los procedimientos con los
que el sistema cuenta. Este manual describe la instalación de la versión de Edimburgo
bajo TOPS-10, versión 7.01, los usuarios de otro tipo de instalación se pueden referir
al apéndice III.
Este manual se basa en la obra "User's Guide to DECsystem-10 Prolog" de L.M.
Pereira, F.C.N. Pereira y D.H.D. Warren. Parte del capítulo 2 se tomo del libro de
Byrd, (80). Comentarios muy útiles que se presentan en este manual fueron realizados
por Lawrence Byrd, Luis Jenkins, Richard O'Keefe, Fernando Pereira, Robert Rae y
León Sterling.
El sistema Prolog es mantenido por el Departamento de Inteligencia Artificial y el
Engineering Board Computing Committee del Science and Engineering Research
Council.
4. CAPÍTULO 1. COMO CORRER PROLOG.
El Prolog para el DECsystem-10 ofrece al usuario un ambiente de programación con
herramientas para construir programas, depurarlos siguiendo su ejecución a cada
paso y modificar partes de los programas sin tener que volver a comenzar todo el
proceso desde el último error.
El texto en un programa en Prolog se crea en un archivo o en un número de archivos
usando cualquier editor de texto estándar. El intérprete de Prolog puede ser instruido
para que lea los programas contenidos en estos archivos. A esta operación se le llama
consultar el archivo.
1.1 Comienzo
Para correr el intérprete de Prolog, se deberá teclear el siguiente comando:
.r prolog
El intérprete responde con un mensaje de identificación y el indicador "| ?- " se
presentará cuando el sistema esté listo para aceptar cualquier entrada, así, la pantalla
se verá como sigue:
Edinburgh DEC-10 Prolog version 3.47
University of Edinburgh, September 1982
| ?-
En este momento el intérprete estará ya esperando que el usuario ingrese cualquier
comando por ejemplo: una pregunta o una orden (ver sección 1.4). No se pueden
introducir cláusulas inmediatamente (ver sección 1.3). Este estado se llama nivel alto
del intérprete. Mientras que se teclea un comando, el indicador permanecerá de la
siguiente manera:
"| ".
El indicador "?-" aparecerá solamente en la primera línea.
5. 1.2 Lectura de archivos con programas
Un programa está compuesto por una secuencia de cláusulas que se pueden intercalar
con directivas para el intérprete. Las cláusulas de un procedimiento no
necesariamente tienen que ser consecutivas, pero si es necesario recordar que el orden
relativo es muy importante.
Para Ingresar un programa desde un archivo, sólo se tiene que teclear el nombre del
archivo dentro de corchetes y seguidos de un ENTER:
| ?- [archivo] ENTER
Esta orden le dice al intérprete que tiene que leer (consultar) el programa. La
especificación del archivo tiene que ser un átomo de Prolog, también puede contener
especificación de dispositivos y/o una extensión, pero no se debe incluir la ruta de
ubicación. Nótese que es necesario que todas estas especificaciones se coloquen dentro
de comillas, por ejemplo:
| ?- ['dska:myfile.pl'].
Después de haber tecleado esto el archivo será leído,
Así las cláusulas que se han guardado en un archivo están listas para ser leídas e
interpretadas, mientras que las directivas son ejecutadas al momento de ser leídas.
Cuando se encuentra el fin del archivo, el intérprete pone en pantalla el tiempo que se
ha gastado en la lectura y el número de palabras ocupadas por el programa. Cuando
este mensaje se despliegue quiere decir que el comando se ha ejecutado con éxito.
También se pueden combinar varios archivos de la siguiente manera:
| ?- [miprograma,archivos_extras,archivos_de_tests].
En este caso los tres archivos serán leídos.
Si un nombre de archivo se antecede del signo "menos" (-), por ejemplo:
| ?- [-tests,-fixes].
Entonces este archivo o estos archivos serán releídos. La diferencia entre la lectura
simple y la releída es que cuando un archivo es leído entonces todas las cláusulas en el
archivo se añaden a la base de datos interna de Prolog, mientras que si se lee el
archivo dos veces, entonces se tendrá una copia doble de todas las cláusulas.
Sin embargo, si un archivo es releído entonces las cláusulas de todos los
procedimientos en el archivo reemplazarán a cualquier cláusula existente
6. anteriormente. La reconsulta es útil para indicarle a Prolog las correcciones que se
realizan a los programas.
1.3 Ingreso de las cláusulas a la terminal
Las cláusulas se puede ingresar directamente en la terminal pero esto es recomendado
solamente cuando éstas no se necesitan permanentemente y cuando son pocas. Para
ingresar cláusulas desde la terminal, se debe dar el comando especial: give the special
command:
| ?- [user].
|
y el indicador "| " mostrara que el intérprete está ahora en el estado de espera de
cláusulas o directivas. Para ingresar al nivel más alto del intérprete se deberá teclear
Control + Z (^Z). Este es el equivalente al fin de archivo de archivo temporal virtual
llamado ‘user’
1.4 Directivas: Preguntas y comandos
Las directivas pueden ser preguntas y/o comandos. Ambas pueden ser formas de
dirigir al sistema para alcanzar alguna o algunas metas. En el siguiente ejemplo, la
función de pertenencia a una lista se defina como sigue:
member(X,[X|_]).
member(X,[_|L]) :- member(X,L).
(Nótese que las variables sin valor asignado se representan como "_".)
La sintaxis completa para una pregunta es "?-" seguida por una secuencia de metas,
por ejemplo:
?- member(b,[a,b,c]).
7. Al nivel más alto del intérprete (en el que el indicador es como este: "| ?- "), una
pregunta se puede teclear abreviando el "?-" que de hecho ya está incluido en el
indicador. Así, en el nivel más alto, una pregunta se vería de la siguiente manera:
| ?- member(b,[a,b,c]).
Recuerde que los términos en Prolog deben terminar con el punto ".", por lo que el
Prolog no ejecutará nada hasta que Usted haya tecleado este punto y un ENTER al
final de la pregunta.
Si la meta que se ha especificado en la pregunta puede ser satisfecha, y si no hay
variables a considerar como en este ejemplo, entonces el sistema responde:
yes
Y la ejecución de la pregunta ha terminado.
Si en la pregunta se incluyen variables, entonces el valor final de cada variable es
desplegado (excepto para las variables anónimas). Así la pregunta:
| ?- member(X,[a,b,c]).
Podría ser respondida como:
X=a
En este momento el intérprete estará esperando que el usuario introduzca un ENTER
o un punto "." Seguido de un ENTER. Si se teclea solamente ENTER, termina la
pregunta y el sistema responde "yes". Sin embargo, si se teclea ".", el sistema realiza
un backtracking y busca soluciones alternativas. Si no hay soluciones alternativas
entonces el sistema responde:
no
La salida para algunas preguntas como la que se muestra abajo, donde un número
precede a un "_" significa un nombre generado por el sistema para una variable, por
ejemplo:
| ?- member(X,[tom,dick,harry]).
X = tom ;
X = dick ;
X = harry ;
8. no
| ?- member(X,[a,b,f(Y,c)]), member(X,[f(b,Z),d]).
X = f(b,c),
Y = b,
Z=c
yes
| ?- member(X,[f(_),g]).
X = f(_52)
yes
| ?-
En el caso de los comandos, son similares a las preguntas excepto porque:
1. Las variables asociadas con un valor no se muestran cuando la ejecución del
comando tiene éxito.
2. No existe la posibilidad de hacer backtracking para encontrar otras
alternativas.
Los comandos inician con el símbolo ":-" . A nivel alto del intérprete simplemente
basta con iniciar con el indicador "| ?- ". Cualquier salida pretendida debe ser
programada explícitamente, por ejemplo, en el comando:
:- member(3,[1,2,3]), write(ok).
Dirige al sistema para que verifique si el número 3 pertenece a la lista [1,2,3], y a que
redirija la salida hacia la variable "ok" si así es. La ejecución de un comando termina
cuando todas las metas en el comando se han ejecutado de manera exitosa. No se
muestran soluciones alternativas. Si no se encuentra solución entonces el sistema
indica:
?
como una advertencia.
El uso principal de los comandos (en oposición al uso de las preguntas) es permitir a
los archivos el contener las directivas que llaman a varios procedimientos, para los
9. cuales el usuario no requiere salida impresa. En estos casos solo se requiere llamar a
los procedimientos por su efecto, por ejemplo, si no quiere tener interacción con el
intérprete durante la lectura de un archivo. Un ejemplo muy útil es el uso de una
directiva que consulte una lista completa de archivos.
:- [ bits, bobs, main, tests, data, junk ].
Si un comando como este se encuentra contenido en el archivo 'myprog' entonces,
teclear lo siguiente sería la forma más rápida para cargar el programa entero:
| ?- [myprog].
Cuando se interactúa al nivel más alto del intérprete de Prolog, la distinción entre
comandos y preguntas es normalmente muy importante. A este nivel se teclean las
preguntas en forma normal. Solo si se lee de un archivo, entonces para realizar varias
metas se deben usar comandos, por ejemplo; una directiva en un archivo debe ser
precedida por un ":-", de otra manera serían tratados como cláusulas.
1.5 Errores de sintaxis
Los errores de sintaxis se detectan durante la lectura. Cada cláusula o directiva, o en
general cualquier término leído de un archivo o de la terminal, que provoca una falla,
es desplegado en la terminal tan pronto como es leído por el sistema. Una marca
indica el punto en la cadena de símbolos en donde al parser ha fallando en su análisis,
por ejemplo:
member(X,X:L).
Implica que el parser muestre:
*** syntax error ***
member(X,X
*** here *** : L).
Si el operador "." No ha sido declarado como operado infijo.
Nótese que no se muestran comentarios que especifiquen el tipo de error. Si usted
tiene duda acerca de qué cláusula contiene el error, puede usar el predicado:
listing/1
10. Para enlistar todas las cláusulas que han sido eficientemente leídas, por ejemplo:
| ?- listing(member).
1.6 Predicados no definidos.
El sistema puede ocasionalmente también darse cuenta de que se han llamado a
predicados que no tienen cláusulas, el estado de esta facilidad de identificación puede
ser:
- 'trace', que causa que este tipo de errores sean desplegados en la terminal y que el
proceso de depuración muestre la última vez que ha ocurrido.
O,
- 'fail', que causa que estos predicados fallen (este es el estado por default)
El predicado evaluable:
unknown(OldState,NewState)
unifica a OldState con el estado actual y coloca a State como Newstate. Este falla si los
argumentos no son los apropiados. La depuración de predicados evaluables imprime
el valor de este estado a lo largo de otras informaciones. Es importante notar que se
lleva casi un 70% más de tiempo correr al intérprete en esta modalidad. Se espera que
esta facilidad se pueda mejorar en el futuro.
1.7 Ejecución de programas e interrupción de éstos.
La ejecución de un programa se inicia cuando se le da al intérprete una directiva en la
cual se contienen llamadas a uno de los procedimientos del programa.
Sólo cuando la ejecución de una directiva se ha realizado por completo es cuando el
intérprete va por otra directiva. Sin embargo, uno puede interrumpir la ejecución
normal de ésta al presionar ^C mientras el sistema está corriendo. Esta interrupción
tiene el efecto de suspender la ejecución y se despliega el siguiente mensaje:
function (H for help):
11. En este momento el intérprete acepta comando de una sola letra, que correspondan
con ciertas acciones. Para ejecutar una acción simplemente se deberá teclear el
carácter correspondiente (en minúsculas) seguido de un ENTER. Los posibles
comandos son:
a aborta el comando actual tan pronto como sea posible
b detiene la ejecución (ver apartado 1.9)
c continuar la ejecución
e Salir de prolog y cerrar todos los archivos
g manejar posibilidad de recobrar aspectos ya borrados con anterioridad
h Lista todos los comandos disponibles
m Sale a nivel Monitorl (puede teclear Monitor o CONTINUE)
n desactiva el comando trace
t activa el comando trace
Nótese que no es posible romper la ejecución directamente desde el código compilado,
cada una de las opciones "a", "b" y "t" piden que el intérprete realice una acción, por
lo que la acción se seguirá realizando hasta que el código compilado devuelva en
mando al intérprete.
Note también que la opción ("a") no saca al usuario cuando está en modo [user] es
decir insertando cláusulas desde la terminal. Tiene que teclear ^Z para parar este
proceso o teclear ("e") para poder salir de Prolog.
Si se trata de abortar un programa con ^C y accidentalmente se llegara al nivel
Monitor, (quizá porque se apretó ^C demasiadas veces), deberá teclear CONTINUE
para regresar a la interrupción ^C.
12. 1.8 Salida del intérprete
Para salir del intérprete y regresar al nivel monitor, deberá teclear ^Z a nivel alto del
intérprete, o llamar al procedimiento "halt" (pre-cargado por default), o usar el
comando "e" (exit) seguido de una interrupción ^C. ^Z es diferentes de los otros dos
métodos de salida, ya que imprime algunas estadísticas. Es posible regresar usando el
comando del Monitor CONTINUE después de ^Z, si se arrepiente de haber querido
salir.
1.9 Ejecuciones anidadas, Break y Abort.
El sistema prolog integra una manera de suspender la ejecución de un programa e
introducir nuevas directivas o cambiar metas a nivel alto del intérprete. Esto se logra
si se llama a un predicado evaluable Break o tecleando "b" después de una
interrupción con ^C ( ver sección 1.7).
Esto causa que el intérprete suspenda la ejecución inmediatamente, a lo que el
intérprete contestará
[ Break (level 1) ]
Que quiere decir que se ha parado desde el nivel Break y que se pueden introducir
preguntas de la misma manera en que se había hecho desde el nivel alto del intérprete.
El intérprete indica el nivel Break en el que el sistema se encuentra (por ejemplo, en
Breaks anidados), imprimiendo el nivel de Break después de un yes o un no final a las
preguntas. Por ejemplo, a nivel Break 2, se podría ver la respuesta así: uld look like:
| ?- true.
[2] yes | ?-
Un ^Z cierra la interrupción que se había realizado y se reanudan las tareas. Una
ejecución suspendida se puede abortar usando la directiva:
| ?- abort.
Dentro de un break.
En este caso no se necesita un ^Z para cerrar el break; Todos los niveles break se
descartarán y se llega automáticamente al nivel más alto del intérprete.
13. 1.10 Guardado y restauración de estados de programas
Una vez que el programa ha sido leído, el intérprete tendrá disponible toda la
información que sea necesaria para su ejecución, esta información se llama estado de
programa.
El estado de un programa se puede grabar en disco para ejecuciones posteriores. Para
guardar un programa en un archivo, realice la siguiente directiva:
| ?- save(file).
Este predicado se puede llamar en cualquier momento, por ejemplo, puede ser muy
útil a fin de guardar los estados de un programa inmediatos.
Cuando el programa ha sido guardado en un archivo, la siguiente directiva
recuperaría el estado de un programa:
| ?- restore(file).
Después de esta ejecución el programa entonces podrá ser regresado exactamente al
mismo punto en que se dejó al momento de salvarlo la ´´ultima vez.
| ?- save(myprog), write('programa restaurado').
Así, al ejecutarse esta orden con el mensaje propio del usuario, se obtendrá el mensaje
"programa restaurado", por ejemplo.
Nótese que cuando una versión de Prolog nueva se instala, todos los programas
grabados se vuelven obsoletos.
1.11 Inicialización
Cuando prolog inicia busca un archivo llamado 'prolog.bin' en la ruta default del
usuario. Si se encuentra este archivo entonces se asume que el estado de un programa
se ha restaurado. El efecto es el mismo que si se hubiera tecleado:
| ?- restore('prolog.bin').
14. Si no se encuentra este archivo, entonces se busca en forma similar, el archivo
'prolog.ini'. Si se encuentra este archivo, sería lo mismo que haber tecleado:
| ?- ['prolog.ini'].
La idea de este archivo es que el usuario pueda tener aquí los procedimientos y
directivas que se usan usualmente de manera que no se tengan que teclear cada vez
que se necesiten.
El archivo 'prolog.bin' parece ser que es útil cuando se usa con el plsys(run(_,_)) que
permite correr otros programas desde dentro de Prolog. Esta facilidad permite
también regresar al mismo punto si se corrió prolog desde otros programas. En este
caso, el predicado evaluable save/2 debe ser usado para salvar el estado del programa
dentro de 'prolog.bin'.
1.12 Entrada a prolog
Cuando se entra a prolog directamente en una interacción con la terminal, se escribe
automáticamente el archivo 'prolog.log' en modo append. Esto es, si ya existe el
archivo, en el directorio del usuario, entonces la nueva información se añade, si no es
así, y el archivo no existe, entonces el archivo se crea.
CAPÍTULO 2 DEPURACIÓN
Este capítulo describe las facilidades de depuración que están disponibles en el
intérprete de Prolog, el propósito de estas facilidades es proveer información
concerniente al control del flujo del programa, las principales facilidades del paquete
de depuración son las siguientes:
- El Procedure_Box, modelo de ejecución de prolog que permite visualizar con
fines de control el flujo del programa, especialmente durante un backtracking.
El control de flujo se considera un nivel de procedimiento más que un
procedimiento a nivel de cláusulas individuales.
- La habilidad para rastrear el programa o un grupo selectivo de puntos de
este. Puntos espías que pueden ser procedimientos básicos o principales, en los
que el programa deberá tener para que el usuario pueda interactuar con el
sistema.
- La posibilidad de contar con opciones de control y de información,
disponibles durante la depuración.
15. 2.1 El modelo de flujo del procedimiento Box Control
Durante la depuración de un programa se imprimen una secuencia de metas
defectuosas a fin de mostrar el estado que el programa ha alcanzado durante esta
ejecución. Sin embargo, para poder entender qué es lo que ocurre, es necesario
entender primero cuándo y por que el intérprete imprime las metas. Como en otros
lenguajes de programación, existen puntos principales de interés como los
procedimientos de entrada y regreso, pero en Prolog, existe el backtracking que tiene
una complejidad adicional. Una de las principales confusiones para los novatos es que
los programadores tienen que enfrentar la cuestión de qué es lo que está ocurriendo
en el momento en que una meta falla y el sistema inicia repentinamente el
backtracking. El modelo Box de Prolog permite que el flujo del programa posibilite
identificar cualquier punto en la ejecución en términos de localización en el texto del
programa. Este modelo provee las bases para la depuración de un programa.
Examinando a fondo un procedimiento se tiene:
*--------------------------------------* Call | | Exit ---------> + descendant(X,Y) :-
offspring(X,Y). + ---------> | | | descendant(X,Z) :- | <--------- + offspring(X,Y),
descendant(Y,Z). + <--------- Fail | | Redo *--------------------------------------*
La primer cláusula establece que Y es descendiente de X si Y es resultado de X, y la
segunda cláusula establece que Z es descendiente de X si Y lo es de X y si Z es
descendiente de Y. En el diagrama, una caja se ha dibujada al rededor de todo el
procedimiento y se han estipulado flechas para establecer el flujo de control con
entradas y salidas de esta caja. Existen cuatro flechas por examinar durante el flujo:
• Call que representa la llamada inicial del procedimiento. Cuando una meta es
de la forma descendant(X,Y) se requiere que el centro pase a través de la caja
para ver un componente que haga matching y que de satisfacción a alguna de
las submetas en el cuerpo de la cláusula. Este procedimiento es independiente
del matching posible que se dé, por ejemplo, en la primera etapa se llama y el
matching tiene lugar. Podemos imaginar textualmente el movimiento del
código cuando se llega a una llamada de descendencia en alguna parte del
código.
• Exit. Esta flecha representa un regreso exitoso del procedimiento. Este ocurre
cuando la meta inicial ha sido unificada con uno de los componentes clausales y
cualquiera de las submetas ha sido satisfecha. El control entonces pasa por el
puerto de salida de la caja de descendencia. Textualmente se para el código
siguiente por descendencia y se regresa por el camino por donde se llegó.
• Redo. Esta flecha indica que la meta subsiguiente ha fallado y que el sistema
hace backtracking en un intento de encontrar alternativas a soluciones previas.
El control pasa a través del puerto Redo de la caja de descendencia. Se hace un
16. intento entonces por resatisfacer una de las submetas componentes en el
cuerpo de la última cláusula en la que se ha tenido éxito, si esto falla, se hace un
completo, nuevo, matching con la cláusula original al cuerpo de una nueva
cláusula, textualmente se sigue la parte anterior de código de la manera en que
se ha ido buscando nuevas formas de obtener éxito descomponiendo ésta en
otras cláusulas y siguiendo así mientras que sea necesario.
• Fail. Esta flecha representa una falla en la meta inicial, que puede ocurrir si no
existe un matching efectivo con alguna cláusula o si las submetas establecidas
nunca son satisfechas, o si cualquiera de las soluciones producidas es
rechazada siempre por el proceso posterior. El control pasa entonces por la
caja de descendencia en el puerto Fail y el sistema continua en backtracking.
Textualmente el sistema se mueve hacia atrás en el código que ha llamado este
procedimiento y se continua moviéndose hacia atrás buscando puntos de
elección.
En términos de este modelo, la información que nosotros obtenemos acerca de esta
caja de procedimiento, es sólo acerca del flujo de control que se da a lo largo de estos
cuatro puertos. Esto significa que a este nivel no importa con cual cláusula se hace
matching y como se da satisfacción a las submetas, sino más bien como quisiéramos
conocer la meta inicial y la final tienen efecto.
Note que la caja que se ha dibujado para cercar el procedimiento tiene que ser vista
como una caja de invocación, esto es, que hay una caja diferente para cada
procedimiento, obviamente con algunos procesos recurrentes en todos los
procedimiento, con diferentes llamadas y salidas en el flujo de control, pero estas para
diferentes invocaciones. Por ello se les dé una entero único de identificación a cada
una de estas cajas.
2.2 Predicados básicos de depuración.
El intérprete proporciona un rango de predicados evaluables para el control de las
facilidades de evaluación, lo más básicos son los siguientes:
• debug que coloca al modo Debug en On (usualmente está en off) a fin de tener
un rango amplio de control en el flujo de información disponible desde el
principio. Cuando el debug está en off, el sistema no recuerda los llamados
hechos a procedimientos anteriormente, sino que solamente recuerda las
invocaciones que se están ejecutando. Se puede llamar a medias de una
ejecución después de haber dado un ^C ( ver el comando trace) pero la
información anterior no estará disponible.
• nodebug Pone a debug en off, y si existen puntos espías, éstos serán borrados.
• state. Muestra el estado de debug.
17. 2.3 Rastreo
El siguiente comando evaluable puede ser usado a fin de tener un control más efectivo
del programa que se va a depurar.
• trace coloca el switch debug en on, si es que no se ha hecho ya, y asegura que el
siguiente tiempo de control, se desplegará un mensaje y se le pedirá al usuario
que interactue con el sistema. El efecto del rastreo también puede ser logrados
tecleando un "t" después de una interrupción con ^C.
• leash(Mode) coloca el Leashing_Mode en forma de modo, donde modo puede
tener cualquiera de los siguientes valores:
full - prompt on Call, Exit, Redo y Fail
tight - prompt on Call, Redo y Fail
half - prompt on Call y Redo
loose - prompt y Call
off - never prompt
o cualquiera otra combinación de estos y de los puertos descritos en la sección 4.12
El valor inicial del modo es "half", se puede cambiar si se modifica el archivo
"prolog.ini".
2.4 Spy-points
Para programas extensos o de cualquier extensión es prácticamente imposible que el
programador siga paso a paso todo lo que está ocurriendo en cada módulo o en cada
meta o submeta. Es por ello que se posibilita la tarea de formular puntos espías que
permiten mantener un control exacto solamente en puntos clave elegidos por el
usuario para el control total del programa y su curso.
La selección del modo que se realice no tiene injerencia alguna en los puntos espías
que se han elegido por parte del usuario.
Los puntos espías son conjuntos de establecimientos de variables y de predicados
evaluables o también de operadores estándares.
18. spy X Coloca en On el modo de los puntos espías, se puede usar en cualquier
predicado o punto del programa en una oración evaluable o en una directiva
establecida previamente.
nospy X Funciona de manera similar a spy X pero en esta caso x es un conjunto de
puntos espías definidos previamente y removidos.
2.5 Formato de los mensaje de depuración.
Se puede ver el formato que tienen exactamente y a profundidad los mensajes de
error, esto permitirá realizar de una manera más eficiente las tareas de depuración de
programas al posibilitar el conocimiento de los símbolos usados en cada mensaje como
este
** (23) 6 Call : foo(hello,there,_123) ?
"**" indica que es un spy-point. Si este punto no es un conjunto de puntos espías
predefinidos entonces el puntero se convierte en un ">". Que da las siguientes posibles
combinaciones:
"**" un spy-point.
"*>" un spy point con un skip la última vez que se estuvo en la caja
" >" No es un sapy point pero si hubo un skip la última vez que se estuvo en la
caja
" " No es un spy point.
Nótese que en esta caso las acciones son rastreadas a fin de mantener un centro exacto
en el depurado durante el establecimiento de este tipo de mensajes.
2.6 Opciones disponibles durante la depuración
Esta sección describe las opciones particulares que están disponibles cuando el sistema
te muestra después de algún mensaje de depuración. Todas las opciones son una letra
como nemónico, y algunas pueden ser opcionales seguidas de un entero decimal.
Algunas opciones solo requieren el terminador.
19. La única opción que se hace necesaria de recordar es "h" y un ENTER, ya que es la
función que desplegará a las siguientes funciones:
<cr> creep c creep
<lf> leap l leap
<esc> skip s skip
x back to choice point q quasi skip
r retry r <n> retry goal n f fail f <n> fail
goal n
; redo
a abort e salir de Prolog
h help p imprime una meta
w escribe meta d despliega una meta
g imprime ancestros g <n> ultimo n ancestros
@ comando accept b break
[ consult user n nodebug
Las primeras tres opciones son decisiones de control básicas ya que son las más
frecuentemente usadas, cada una puede ser llamada con una sola tecla c <cr> Creep
Causa que el intérprete se vaya de un solo paso al siguiente puerto e imprima un
mensaje. Si el puerto está atascado se le pedirá al usuario acciones posteriores, de otra
manera el procedimiento continuará hasta terminar en la terminal.
l <lf> Leap
Causa que el intérprete regrese ala ejecución del programa, solo para cuando un
punto espía se ha encontrado, o cuando el programa termina
s <esc> Skip
Es válido solamente en el puerto Redo, brinca sobre una ejecución entera o un
procedimiento. El uso de esta directiva de compilación garantiza que el control será
devuelto a la caja de control después de la ejecución dentro de la caja. Cuando se da
20. un cuasibrinco o cuasiskip en donde no se ignoran los puntos espías, la opción "t"
después de un ^C deshabilitará las máscaras.
q Quasi-skip
Es como skip, excepto porque este brinco no oculta a los puntos espías para la
depuración. Si existe un punto espía en la ejecución de una meta, entonces el control
regresa al punto en donde la acción se debe realizar.
x Back to choice point
Da la posibilidad de hacer un fail rápido, llevando al programa al punto real de
decisión anterior. Cuando el usuario sabe que algo hará fail, puede anticipar el punto
de decisión y verificarlo rápidamente sin tener que repetir el proceso completo. Si se
da una secuencia de Fails seguidos de una secuencia de Redos (o si la secuencia está
vacía) se tienen mensajes estándares (como los mostrados anteriormente) excepto
para los primeros dos caracteres donde será "=>". Cuando se llama a la acción exit, la
operación de salida ocurrirá normalmente.
r Retry
Se puede usar en cualquiera de los cuatro puertos, aunque el puerto call no tenga
efecto en realidad. Esto permite restablecer la invocación cuando por ejemplo, el
usuario se ha dado cuenta de que ha obtenido resultados algo raros. El estado de la
ejecución es exactamente el mismo que el alcanzado durante la ejecución normal
original. Si se proporciona un entero con el comando retry entonces se tomará como
una especificación de que el sistema trate de invocar al puerto y no a la caja actual,
por supuesto que a un puerto designado por el usuario. Desafortunadamente el
proceso no puede ser garantizado. Se puede suplir con cuts (!) al interior del
programa. Un mensaje de error "[ ** JUMP ** ]" se desplegará en la terminal para
indicar que es lo que ha ocurrido.
f Fail
es similar al retry ya que transfiere el control al puerto fail desde la caja actual, en
una posición en donde se está cercado a hacer un backtracking
; Redo
Puede ser llamado en una salida y forzar a mover al puerto Redo
a Abort
Causa que se aborte la ejecución actual. Todas las ejecuciones se mantienen hasta que
el usuario se asegura de que no las necesita conforme sube al mas alto nivel del
intérprete (es equivalente al predicado evaluable abort)
21. e Exit from Prolog
Causa una salida irreversible del sistema de prolog, de regreso al Monitor (esta
directiva es equivalente a Halt)
h Help
Despliega la tabla de opciones marcada arriba
p Print goal
Reimprime la meta actual usando print
w Write goal
Escribe la meta actual en la terminal usando write. Esto puede ser muy útil cuando la
lista temporal de rutinas no hace lo que se espera de ella
d Display goal
Despliega la meta en turno, usando display (ver Write arriba).
g Print ancestor goals
Provee al usuario de una lista de las metas o submetas que ya se han cumplido en la
ejecución de un programa, el orden se da en forma jerárquica hacia arriba de la
secuencia que ha sido llamada. Usa los ancestros de cada predicado evaluable (ver
pág. 42). Si se proporciona un entero con esta directiva entonces se despliegan n
antecesores desde el punto en que el usuario ha dado la directiva.
@ Accept command
Permite llamar arbitrariamente a metas de Prolog, es un break de un solo paso muy
efectivo, (ver más adelante). El mensaje inicial "| :- " saldrá en la terminal y el
comando entonces se lee desde la terminal y se ejecuta como si el usuario estuviera en
el nivel más alto.
b Break
Llama al predicado evaluable break, pone al intérprete al nivel más alto con la
ejecución aparte del modulo actual, cuando se finaliza el modo break (^Z) el sistema
pregunta en qué punto se hizo el rompimiento, y una nueva ejecución se separa de la
suspendida, los números de invocación comienzan a partir de 1 desde su invocación.
[ Consult user Permite insertar clausulas y regresar a donde se estaba, es lo mismo
que usar "@" seguida de "[user].".
22. n noebug
Desactiva el modo debug, lo pone en Off, es la forma correcta de apagar el debug
desde el modo de rastreo. No se puede usar "@" o "b" porque siempre activa el
debug desde el regreso.
2.7 Consultar de nuevo durante la depuración.
Es posible y a veces es útil reconsultar o releer un archivo a medias de la ejecución de
un programa. Sin embargo, esto puede ocasionar que el sistema funcione de una
manera impredecible debido a las siguientes circunstancias: un procedimiento se ha
ejecutado con éxito, se ha redefinido por una consulta y después se ha reactivado por
un backtracking. Cuando el backtracking ocurre, todas las clausulas nuevas
aparecerán en el intérprete como posibilidades de alternativa de solución aún cuando
se hayan usado ya clausulas o predicados exactamente iguales, así grandes cantidades
de procedimientos no deseados ocasionarían backtracking. Este problema se evita si
se hace el consult desde el puerto del procedimiento que será redefinido.
CAPITULO 3. COMPILACIÓN
El compilador Prolog DECsystem-10 produce código más compacto y eficiente, el cual
funciona de 10 a 20 veces más rápido que el intérprete de código y requiere mucho
menos almacenamiento en tiempo de ejecución. Los programas compilados con
Prolog, son comparados en eficiencia, con programas desarrollados en LISP ( con el
compilador LISP DECsystem-10 ), para realizar las mismas funciones. En contra de
esto, la desventaja es que la compilación es varias veces más lenta que la "consulta" y
mucha de la ayuda proporcionada por el depurador, como el seguimiento, no es
aplicable al código compilado.
23. 3.1 Llamando al Compilador.
Para compilar el programa se usa la siguiente sentencia :
|? compile (Archivos).
donde "Archivos" es cualquier nombre de archivo ( incluyendo el archivo ‘user’ ), o
una lista de los nombres de los archivos que se desean compilar. Con esta instrucción,
los procedimientos contenidos en dichos archivos serán compilados. A continuación se
muestra un ejemplo con varios archivos :
|? compile ( [dbase, 'extras.pl,' user] ).
Exteriormente, el efecto de "compile" es mucho muy parecido al de "reconsult". Si las
cláusulas para algunos predicados aparecen en más de un archivo, el juego posterior
de dichas cláusulas, sobre-escribe el anterior. La división del programa en archivos
separados no implica ninguna estructura modular ( ningún procedimiento compilado
puede llamar a cualquier otro ).
3.2 Declaraciones Públicas.
Para hacer que un procedimiento compilado sea accesible por el interprete de código (
incluyendo directivas ) es necesario declarar dicho procedimiento como público, lo
cual se realiza con el comando siguiente del compilador :
:- public Predicates.
donde "Predicates" es una especificación del predicado, en la forma : Nombre / Arity
, o también puede ser una conjunción de especificaciones semejantes, por ejemplo :
:- public concatenate/3, member/2, ordered/1, go/0.
Las declaraciones públicas pueden aparecer en cualquier parte dentro de un archivo
compilado; no es necesario que las declaraciones públicas procedan al
correspondiente procedimiento, ni tampoco que estén dentro del mismo archivo.
24. 3.3 Mezclando Código Compilado y Código Interpretado.
Para predicados públicos un procedimiento compilado sobre-escribe cualquier
versión interpretada previa ( cf. reconsult ). De la misma forma una reconsulta
subsecuente de la versión interpretada sobre-escribirá la versión compilada.
Es posible tener un procedimiento compilado con el mismo nombre y arity, así como
muchos y diferentes procedimientos interpretados. Suponiendo que el procedimiento
compilado no fue declarado para ser público, los dos procedimientos nunca
intervendrán el uno con el otro, es decir, el código compilado utilizará la versión
compilada mientras que el interpretador de código usará la versión interpretada.
Cuando una versión compilado se hace presente y sobre-escribe un procedimiento
interpretado, éste último es reemplazado por la cláusula :
P :- incore(P).
donde "P" es el objetivo más general para el predicado y "encore" es un predicado
evaluable estándar ( análogo a "call" ), por medio del cual son accesibles todos los
procedimientos compilados para los predicados públicos.
Hay dos formas en que el código compilado puede utilizar procedimientos
interpretados :
• Si no hay cláusulas compiladas para el predicado, el procedimiento
interpretado es llamado automáticamente.
• La sentencia : call( P ) , siempre llama al intérprete. Es importante hacer
notar, que esto implica que deseamos llamar procedimientos compilados con
"call" , deben ser declarados como públicos.
Para aclarar lo anterior vamos a ver un ejemplo :
Primero compilamos el siguiente archivo: :- public f/1, g/1.
f(a).
g(X) :- f(X).
g(X) :- h(X).
No hay cláusulas para h/1. Después consultamos lo siguiente :
f(b).
h(c).
25. Ahora, si nosotros llamamos a " f "obtendremos :
?- f(X).
X=a;
X=b;
no
Esto es, nosotros usamos tanto las cláusulas compiladas, como las interpretadas para
" f ". De cualquier modo si llamamos a " g " :
?- g(X).
X=a;
X = c ; no
podemos observar que " g " solo llama la versión compilada de " f ", así que la
solución : X = b , no es encontrada. La segunda cláusula para " g " llama a " h ", y
como no hay cláusulas compiladas para " h " esta llamada es pasada al intérprete, el
cual encuentra una solución : X = c.
3.4 Declaraciones de Modo.
Cuando un programa va a ser compilado es conveniente incluir declaraciones de
modo, las cuales informan al compilador que ciertos procedimientos serán usados en
forma restringida, es decir, que algunos argumentos en la llamada siempre serán
"entrada", mientras que otros siempre serán "salida". Esta información habilita al
compilador para ser capaz de generar código más compacto y hacer un mejor uso del
almacenamiento en tiempo de ejecución. Las declaraciones de modo también ayudan
a que otras personas comprendan la operación del programa.
Una declaración de modo es indicada con una directiva del compilador de la forma :
:- mode P(M).
donde "P" es el nombre de un procedimiento y "M" especifica los "modos" de sus
argumentos. M consiste de cierto número de "elementos de modo" ( elementos que
indican el modo deseado para los argumentos ), separados por comas, una para cada
26. posición de los argumentos del predicado interesado. Los elementos de modo pueden
ser de cualquiera de los 3 tipos siguientes :
• Modo + : Especifica que el argumento correspondiente en cualquier llamada al
procedimiento siempre será "entrada".
• Modo - : Indica que el argumento nunca será "entrada", es decir, siempre será
"salida".
• Modo ? : Indica que no hay restricción en la forma del argumento. Por
ejemplo, una declaración de modo como :
:- mode concatenate(?,?,?).
es equivalente a omitir completamente la declaración.
Por ejemplo, si tenemos un procedimiento llamado "encadenar" y los dos argumentos
primeros, siempre serán "entradas", podemos dar la siguiente declaración de modo :
:- mode encadenar(+,+,?).
En el caso de que en el procedimiento anterior el tercer argumento siempre será
"salida", podemos reforzar la declaración de modo como sigue :
:- mode encadenar(+,+,-).
Además, está permitido combinar varias declaraciones de modo en un solo comando,
por ejemplo :
:- mode encadenar(+,+,-), mode miembro(+,?), mode
orden(+).
Para que una declaración de modo tenga efecto debe aparecer antes de las cláusulas
del procedimiento correspondiente. Cuando una declaración de modo es violada por
una llamada a un procedimiento, la forma precisa como reacciona depende de el
objetivo y la cabecera de la cláusula. La llamada puede ser realizada con éxito como si
no hubiera una declaración de modo o puede causar un mensaje de error y fracasar.
Como el resultado preciso depende de los cambios en las versiones futuras del sistema,
el usuario debió asumir que todas las declaraciones de modo que son violadas
causaran un mensaje de error.
Las declaraciones de modo son ignoradas por el intérprete.
27. 3.5 Indexación.
En contraste con el interprete, las cláusulas de un procedimiento compilado son
indexadas de acuerdo al "functor" principal del primer argumento especificado en la
cabecera de la cláusula. Esto significa que el subconjunto de cláusulas, son
emparejadas con un determinado objetivo, hasta que el primer paso de la unificación
interesada, sea hallada muy rápidamente, prácticamente en un tiempo constante ( un
tiempo independiente del número de cláusulas ). Esto puede ser muy importante
donde hay un gran número de cláusulas en un procedimiento.
La indexación también mejora la capacidad del sistema de Prolog para detectar la
determinacia, la cual es importante en la conservación del espacio de trabajo.
3.6 Optimización de la cola de Recursión.
El compilador incorpora "Optimización de la cola de recursión" para mejorar la
velocidad y la eficiencia del espacio de procedimientos determinados. Cuando la
ejecución alcanza el último objetivo en una cláusula que pertenece a un
procedimiento, y provee que no hay puntos de retro seguimiento restantes, todo el
espacio de trabajo asignado a los procedimientos locales es reclamado ANTES de
finalizar la llamada y cualquier estructura que fue creada es eliminada. Esto significa
que el programa puede realizar varias recursiones sin necesidad de exceder el límite
en el espacio.
Por ejemplo :
cycle(State) :- transform(State,State1),
cycle(State1).
donde "transform" es un procedimiento determinado, puede continuarse ejecutando
indefinidamente, previendo que cada estructura individual "State", no sea demasiado
larga. El procedimiento "cycle" es equivalente a un lazo iterativo de in lenguaje
convencional.
Para lograr la ventaja de la optimización de la cola recursiva, debemos asegurarnos
que Prolog pueda reconocer que el procedimiento es determinado en el punto donde la
llamada recursiva toma lugar. Dicho en otra forma, el sistema debe ser capaz de
detectar que no hay otras soluciones que deben ser encontradas para el objetivo
actual, mediante subsecuente retro seguimiento. En general esto está envuelto en la
indexación del compilador Prolog DEC-10 y / o el uso de corte.
28. 3.7 Limitaciones Prácticas.
Algunas consideraciones que debemos tomar en cuenta son :
• Hay que tener presente que el espacio ocupado por el código compilado,
cuando es reemplazado, no es reclamado.
• Hacer que un predicado sea público, nos lleva a generar una cantidad
significante de código extra. Además este código es regenerado cada vez que el
compilador es llamado ( con lo cual el espacio ocupado por el código anterior
no es reclamado ). Debido a esto, es conveniente, cuando sea posible, incluir en
una sola llamada al compilador, todos los archivos que se desea sean
compilados.
• La ejecución del código compilado no puede ser "rota" o "abortada", lo cual si
s posible con el código interpretado. Una respuesta a la petición para
"abortar", realizada con CTRL+C, solo tiene efecto en la siguiente entrada de
un procedimiento interpretado.
Se puede notar que hay una pausa despreciable en el inicio y terminación de la
compilación. Esto se debe a que el compilador reside en una capa separada, la cual
debe ser intercambiada.