Técnicas básicas de programación Prolog -...

34
Técnicas básicas de programación Prolog Programación Declarativa Pablo López 25 de octubre de 2007 1. Programación recursiva La recursión es una técnica fundamental en Prolog, tanto para repre- sentar los datos como para procesarlos. Esto se debe a que Prolog, como todos los lenguajes declarativos puros, carece de iteración. Aunque la programación recursiva es una técnica que nos resulta fami- liar, la recursión en Prolog exhibe ciertas particularidades que se apartan lo habitual. Por ejemplo, el caso base y el caso recursivo no tienen por qué ser excluyentes, lo que permite generar respuestas múltiples. Esta sección ilustra el empleo de la recursión en Prolog a través un ejemplo simple pero eficaz: la axiomatización de la aritmética de Peano. Los objetos a representar serán los números naturales {0, 1, 2, 3...} y las relaciones serán las operaciones aritméticas básicas: suma, resta, producto... Además de la recursión (de datos y código), introduciremos los con- ceptos de modo de un argumento, y uso, comportamiento y significado de un predicado. Ejemplo: Axiomatización de la aritmética de Peano El dominio de los naturales. El conjunto de los naturales N se define inductivamente como sigue: 1. 0 N [Caso Base] 2. si x N, entonces σ( x) N [Caso Recursivo] donde σ es la función sucesor σ : N N; es decir, σ( x) representa al sucesor de x. Las anteriores definiciones permiten construir el conjunto N partiendo del caso base (1), obteniendo nuevos elementos aplicando el caso recursivo (2): N = { 0 base , σ(0), σ(σ(0)), σ(σ(σ(0))), ... recursivo }. Notas de clase Octubre 2007

Transcript of Técnicas básicas de programación Prolog -...

Page 1: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog

Programación DeclarativaPablo López

25 de octubre de 2007

1. Programación recursiva

La recursión es una técnica fundamental en Prolog, tanto para repre-sentar los datos como para procesarlos. Esto se debe a que Prolog, comotodos los lenguajes declarativos puros, carece de iteración.

Aunque la programación recursiva es una técnica que nos resulta fami-liar, la recursión en Prolog exhibe ciertas particularidades que se apartanlo habitual. Por ejemplo, el caso base y el caso recursivo no tienen por quéser excluyentes, lo que permite generar respuestas múltiples.

Esta sección ilustra el empleo de la recursión en Prolog a través unejemplo simple pero eficaz: la axiomatización de la aritmética de Peano.Los objetos a representar serán los números naturales {0, 1, 2, 3...} y lasrelaciones serán las operaciones aritméticas básicas: suma, resta, producto...

Además de la recursión (de datos y código), introduciremos los con-ceptos de modo de un argumento, y uso, comportamiento y significado de unpredicado.

Ejemplo: Axiomatización de la aritmética de Peano

El dominio de los naturales. El conjunto de los naturales N se defineinductivamente como sigue:

1. 0 ∈ N [Caso Base]

2. si x ∈ N, entonces σ(x) ∈ N [Caso Recursivo]

donde σ es la función sucesor σ : N → N; es decir, σ(x) representa alsucesor de x. Las anteriores definiciones permiten construir el conjuntoN partiendo del caso base (1), obteniendo nuevos elementos aplicando elcaso recursivo (2):

N = { 0︸︷︷︸base

, σ(0), σ(σ(0)), σ(σ(σ(0))), ...︸ ︷︷ ︸recursivo

}.

Notas de clase Octubre 2007

Page 2: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.2 Técnicas básicas de programación Prolog

Los naturales así definidos son recursivos: un natural mayor que 0 seráde la forma σ(x); es decir, «contiene» otro natural x más pequeño que él.Por lo tanto, debemos encontrar una estructura de datos recursiva pararepresentar los naturales.

En la mayoría de los lenguajes imperativos la recursión de datos debeser simulada mediante punteros o mecanismos similares. Por el contrario,la recursión de datos puede emplearse en Prolog de forma directa. Enconcreto, para representar los naturales en Prolog basta emplear térmi-nos Prolog recursivos que capturen la definición inductiva de N. El casobase de N nos asegura la existencia de un objeto concreto y único: el 0. Pa-ra representar este objeto en Prolog utilizaremos una constante c. El casorecursivo introduce una función σ : N → N. La expresión σ(x) es un ob-jeto compuesto que debe ser representado por una estructura de aridad 1.Tomaremos una estructura de functor s/1. Los naturales se representaránentonces por términos Prolog que se ajusten a la gramática GN:

N ::= c [Caso Base]| s(N) [Caso Recursivo]

donde el functor s/1 es siempre aplicado a un término que representa unnatural bien formado. Los siguientes son ejemplos de términos Prolog

que representan naturales bien formados; es decir, que se ajustan a la gra-mática GN:

cs(c)s(s((c)))s(s(s(c)))

Un natural n ∈ N se representa aplicando n veces el functor s/1 a laconstante c, lo que denotaremos de forma abreviada por sn(c).

El programador no puede comunicar de ninguna forma a Prolog quetiene la intención de representar los naturales por términos que se ajus-ten a la gramática GN. Esto se debe a que Prolog carece de definicionesde tipos. Los siguientes son términos Prolog que no son naturales bienformados, pues el functor s/1 no siempre se aplica a un natural bien for-mado:

s(s(f(c)))s(s(a))

Aunque no es posible restringir el functor s/1 de forma que siempre seaplique correctamente, sí podemos definir una relación es_natural/1 quese satisfaga cuando su único argumento sea un natural bien formado.

El predicado es_natural/1 podría definirse extensionalmente (por enu-meración de todos los casos):

Notas de clase Octubre 2007

Page 3: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.3

es_natural(c).es_natural(s(c)).es_natural(s(s(c)))....

El problema de esta definición es que deberíamos introducir un núme-ro infinito numerable de hechos para completarla. Parece más apropiadodefinir es_natural/1 intensionalmente, determinando las condiciones quedebe cumplir un término Prolog para ser un natural bien formado. Bastarazonar siguiendo la definición inductiva de N:

1. 0 ∈ N [Caso Base]

El 0 es un elemento de N. Esto es cierto incondicionalmente, por loque se traduce a Prolog el por hecho:

es_natural(c).

donde reemplazamos 0 por su representación Prolog, c.

2. si x ∈ N entonces σ(x) ∈ N [Caso Recursivo]

Este enunciado incluye una condición y una conclusión, por lo quese traduce a Prolog por la regla:

es_natural(s(X)):- es_natural(X).

donde reemplazamos σ(x) por s(X).

de donde obtenemos la definición:

es_natural(s).es_natural(s(X)) :- es_natural(X).

El predicado es_natural/1 así definido es una definición de dominioo tipo, pues define el conjunto de términos que representan un dominio(tipo) dado. Para comprobar el funcionamiento de es_natural/1, bastaresolver el objetivo:

:- es_natural(s(s(s(c)))).

al que Prolog responderá afirmativamente. Por el contrario, Prolog res-ponderá negativamente a los siguientes objetivos:

:- es_natural(s(s(s(0)))).:- es_natural(s(f(s(0)))).

También es posible plantear un objetivo donde aparezca una variable:

:- es_natural(X).

Notas de clase Octubre 2007

Page 4: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.4 Técnicas básicas de programación Prolog

1 es_natural(c).2 es_natural(s(X)):- es_natural(X).

1) es_natural(X)

2. éxito{X/c}

1 - {X/c}

3) es_natural(X1)2 - {X/s(X1)}

4. éxito{X/s(c)}

1 - {X1/c}

5) es_natural(X2)2 - {X1/s(X2)}

6. éxito{X/s(s(c))}

1 - {X2/c}

7) es_natural(X3)...

2 - {X2/s(X3)}

Figura 1: Árbol de búsqueda del objetivo es_natural(X).

Intuitivamente, estamos preguntando a Prolog si existe algún X tal queX es un natural bien formado. Obviamente, existen un número infinitonumerable de términos X que son naturales bien formados, y vemos queProlog los genera en orden creciente:

:- es_natural(X).X= c;X= s(c);X= s(s(c));...

La figura 1 muestra el árbol de búsqueda del objetivo es_natural(X). Pue-de apreciarse que la rama más a la derecha es infinita, por lo que el ob-jetivo nunca termina. Además, cada respuesta computada se obtiene deforma progresiva, introduciendo variables (Xi) y sustituciones (Xi/s(Xi+1))intermedias. Para derivar —o construir— el natural n, basta descender nveces a la derecha (aplicando la regla recursiva) y descender finalmente ala izquierda (aplicando el caso base). Este comportamiento se debe a queal resolver los subobjetivos es_natural(Xi), el caso base y el caso recursivono son excluyentes. Obtenemos un comportamiento similar si planteamosun objetivo con un argumento parcialmente instanciado:

:- es_natural(s(s(X))).X= c;X= s(c);X= s(s(c));...

Modo de un argumento. Los objetivos anteriores realizan funciones muydistintas a pesar de invocar al mismo procedimiento. La razón es que el

Notas de clase Octubre 2007

Page 5: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.5

papel jugado por los argumentos pasados al procedimiento es diferente.Un argumento o parámetro actual de un procedimiento puede estar endos modos:1

Modo + el argumento no tiene variables libres o las variables que contieneno van a resultar instanciadas como resultado de la ejecución

Modo - el argumento tiene variables libres que resultan instanciadas alejecutar el procedimiento

El argumento del objetivo es_natural(s(s(s(c)))) no contiene variables,por lo que está en modo +. Por el contrario, los argumentos de los objetivoes_natural(X) y es_natural(s(s(Y))) contienen variables que resultaninstanciadas en el proceso de resolución, por lo que están en modo -.

Simplificando, podemos identificar el modo + con los parámetros deentrada y el modo - con los parámetros de salida y entrada/salida. En loslenguajes imperativos tradicionales, el que un parámetro sea de entradao salida depende exclusivamente de cómo se defina el parámetro formalen la cabecera del procedimiento. Por el contrario, en Prolog, el que unparámetro sea de entrada o salida depende del propio parámetro actual yde cómo se defina el parámetro formal correspondiente en la cabecera delpredicado. Esto es consecuencia de emplear la unificación como mecanis-mo para el paso de parámetros.

Uso, comportamiento y significado de un predicado. Como hemos vistoen el ejemplo anterior, un predicado puede comportarse de forma diferen-te —e incluso no funcionar— según el modo en que se encuentren susparámetros actuales. Se llama uso de un predicado a la combinación de losmodos de los parámetros actuales. Por ejemplo, el predicado es_naturaltiene dos usos diferentes: es_natural(+) y es_natural(-), según su úni-co argumento esté en modo + o -. Puesto que un parámetro puede estar endos modos diferentes, un predicado con n argumentos puede tener hasta2n usos diferentes.

La forma en que actúa un predicado para un uso concreto se denomi-na comportamiento. El comportamiento clasifica a los predicados según elnúmero de respuestas que generan en test o generador.

Un predicado se comporta como un test si la respuesta computada estávacía; es decir, no se instancia ninguna variable. Esto quiere decir que laúnica respuesta que se obtiene de un test es éxito o fracaso. Por ejemplo,el objetivo es_natural(s(s(c))) se comporta como un test, pues su úni-co argumento está en modo +. El test corresponde siempre al uso en que

1Estas definiciones de modo no se ajustan a las que aparecen en el estándar ISO deProlog y no son completamente precisas. Para simplificar, nos ceñiremos a ellas a lo largodel curso.

Notas de clase Octubre 2007

Page 6: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.6 Técnicas básicas de programación Prolog

todos los argumentos del predicado están en modo +, pues de lo contra-rio algún argumento resultaría instanciado y la respuesta computada noestaría vacía.

Un predicado se comporta como un generador si las respuestas compu-tadas no están vacías. Para ello, al menos un argumento debe estar enmodo -. Según el número de respuestas computadas generadas, tendre-mos un generador único (una sola respuesta), acotado (un número finito derespuestas) o no acotado (infinitas respuestas). El objetivo es_natural(X)de la figura 1 se comporta como un generador no acotado. Llamaremosgenerador anómalo a aquel generador que es incapaz, por construcción, degenerar la secuencia completa de respuestas computadas esperada (nor-malmente, porque el árbol de búsqueda incluye más de una rama infinita).Un ejemplo de generador anómalo es el predicado p/2:

p(X,Y) :-es_natural(X),es_natural(Y).

Intuitivamente, p(A,B) debería devolvernos todos los pares (A, B) talesque A, B ∈ N. Sin embargo, sólo nos devuelve pares (0, B) con B ∈ N:

?- p(A,B).A= cB= c;A= cB= s(c);A= cB= s(s(c));...

Por último, llamaremos significado a la acción particular llevada a cabopor el predicado en cierto uso. El significado suele describirse indicando laclase de términos para los que tiene éxito (en el caso de tests), o el patrónque siguen las respuestas computadas (en el caso de generadores.)

Todos los posibles usos, comportamientos y significados de un predi-cado pueden recogerse en una tabla de comportamiento. Una tabla de com-portamiento tiene tres columnas. En la primera aparece el uso, en la segun-da el comportamiento y en la tercera y última el significado. La figura 2muestra la tabla de comportamiento del predicado es_natural/1.

Normalmente un programador Prolog competente suele anotar losusos admisibles de un predicado como parte de la documentación de unprograma. Algunas implementaciones de Prolog disponen de la directivamode para declarar los usos soportados por un predicado. La directiva:

:- mode nombre_pred(modo_1, ..., modo_n).

Notas de clase Octubre 2007

Page 7: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.7

es_natural(X)uso comportamiento significado+ test X ∈ N

- generador no acotado X = c, s(c), s(s(c)), . . .

Figura 2: Tabla de comportamiento de es_natural/1.

declara un uso válido de nombre_pred/n, indicando para cada argumentoel modo en que puede aparecer, que puede ser + o -. Si se intenta utilizarun predicado en un uso que no ha sido declarado mediante la correspon-diente directiva mode, el compilador de Prolog puede generar una adver-tencia.2 Podríamos declarar los usos válidos del predicado es_natural/1mediante las directivas:

:- mode es_natural(+).:- mode es_natural(-).

que declaran que es_natural/1 puede emplearse en usos + o -. La direc-tiva mode soporta una abreviatura, ?, que indica que el argumento puedeaparecer indistintamente en modo + o -, lo que permite especificar variosusos válidos con una sola directiva mode. Por ejemplo, los usos válidos dees_natural/1 se pueden declarar mediante la directiva:

:- mode es_natural(?).

puesto que el predicado funciona independientemente del modo en quese encuentre su argumento. Aunque una implementación de Prolog nosoporte declaraciones de modo, es buena idea añadirlas al programa enforma de comentario:

% :- mode es_natural(?).es_natural(c).es_natural(s(X)) :- es_natural(X).

Así aparecen descritos los predicados en casi todas las librerías Prolog.3

Ejercicios

1. ¿Cómo se comporta es_natural/1 si intercambiamos el orden delhecho y la regla?

es_natural(s(X)) :- es_natural(X).es_natural(c).

2Son pocos los compiladores de Prolog que soportan la directiva mode y más escasosaún los que comprueban los usos correctos en tiempo de ejecución.

3SWI-Prolog no soporta la directiva mode; sin embargo, emplea la notación +, -, ? en ladocumentación de los parámetros de los predicados predefinidos.

Notas de clase Octubre 2007

Page 8: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.8 Técnicas básicas de programación Prolog

En concreto, ¿cómo afecta esto a las semánticas declarativa y opera-cional del programa? ¿Influye el orden en que aparecen los hechos yreglas en un programa?

2. ¿Qué representa el término s(s(X))? ¿Cómo se comporta el objeti-vo es_natural(s(s(X)))? ¿En qué modo se encuentra el argumentos(s(X))?

3. Define los predicados par/1 e impar/1 utilizando recursión directa yrecursión mutua. Construye las tablas de comportamiento y comparalas semánticas declarativa y operacional.

Relaciones aritméticas básicas. Una vez representados los objetos (losnaturales) mediante términos Prolog, parece razonable preguntarse quétipo de relaciones entre naturales nos interesan y cómo podemos repre-sentarlas mediante procedimientos (hechos y reglas). Las relaciones entrenaturales serán operaciones (+,−,×, ...) y relaciones (<, >, =, ...) aritméti-cas básicas. Estas operaciones y relaciones se definen habitualmente comofunciones:

+ : N×N → N

− : N×N → N

...

Prolog no permite definir funciones, sino predicados o relaciones. Estosignifica que debemos pasar de un estilo funcional a un estilo relacional.La ventaja del estilo relacional es que, como veremos, desaparecen losconceptos de entrada y salida.

Comenzaremos definiendo la suma de naturales como una relación ter-naria tal que suma(X,Y,Z) se satisface cuando Z = X + Y. Puesto que losobjetos relacionados (naturales) son recursivos, lo normal será que la rela-ción suma/3 sea recursiva. En general, para definir una relación recursivaaplicaremos recursión a algunos de sus argumentos (o a alguna relaciónentre sus argumentos). En el caso de suma/3, podemos aplicar recursión alprimer argumento, que podrá ser c (caso base) o s(X) (caso recursivo), dedonde obtenemos el esquema:

suma(c,?,?) :- ... [Caso Base]suma(s(X),?,?) :- ... [Caso Recursivo]

Ahora debemos determinar qué términos corresponden a los otros argu-mentos, señalados en el esquema con el símbolo ?, así como las condicio-nes que debe verificar cada caso de la relación.

El segundo argumento puede ser cualquier natural y no es necesariorazonar según su valor. Por lo tanto, podemos utilizar una variable Y:

Notas de clase Octubre 2007

Page 9: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.9

suma(c,Y,?) :- ... [Caso Base]suma(s(X),Y,?) :- ... [Caso Recursivo]

El caso base es fácil de resolver: su tercer argumento es la suma de c e Y;es decir, Y. Además, 0 + Y = Y se satisface incondicionalmente, por lo quebasta emplear un hecho:

suma(c,Y,Y). [Caso Base]suma(s(X),Y,?) :- ... [Caso Recursivo]

El caso recursivo es sólo un poco más complicado. Por ser recursivo, debecontener una llamada a sí mismo:

suma(c,Y,Y). [Caso Base]suma(s(X),Y,?) :- suma(?,?,?),... [Caso Recursivo]

La cláusula recursiva se emplea para resolver (X + 1) + Y. La llamadarecursiva debe resolver una suma menor, más próxima al caso base. Enparticular, puesto que estamos aplicando recursión sobre el primer argu-mento, X + 1, la invocación recursiva debe calcular la suma X + Y, dedonde:

suma(c,Y,Y). [Caso Base]suma(s(X),Y,?) :- suma(X,Y,?),... [Caso Recursivo]

Supongamos que sabemos la solución de X + Y, a la que llamaremos Z.Entonces obtenemos:

suma(c,Y,Y). [Caso Base]suma(s(X),Y,?) :- suma(X,Y,Z),... [Caso Recursivo]

Ahora bien, si X + Y = Z, entonces (X + 1) + Y = Z + 1, luego:

suma(c,Y,Y). [Caso Base]suma(s(X),Y,s(Z)) :- suma(X,Y,Z). [Caso Recursivo]

Podemos plantear ahora la resolución del objetivo:

?- suma(s(s(c)),s(c),Z).

que devolverá como única respuesta computada Z=s(s(s(c))).A pesar de las apariencias, el predicado suma/3 no está correctamente

definido. Para entender por qué, basta resolver el objetivo:

?- suma(c, f(a), Z).

que devuelve como única respuesta computada Z=f(a). Esto es incorrecto,pues la relación suma/3 sólo debe satisfacerse entre tres naturales. Prolog

es incapaz de reconocer este error por carecer de tipos. Para subsanarlobasta añadir una comprobación de dominio al caso base:

suma(c,Y,Y):- es_natural(Y). [Caso Base]suma(s(X),Y,s(Z)) :- suma(X,Y,Z). [Caso Recursivo]

Notas de clase Octubre 2007

Page 10: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.10 Técnicas básicas de programación Prolog

1) suma(s(s(c)), s(c), s(s(s(c))))

2) suma(s(c), s(c), s(s(c)))

2 - {X1/s(c), Y1/s(c), Z1/s(s(c))}

3) suma(c, s(c), s(c))

2 - {X2/c, Y2/s(c), Z2/s(c)}

4) es_natural(s(c))

1 - {Y3/s(c)}

5) es_natural(c)

4 - {X4/c}

6. éxito

{}

3 -

Figura 3: Uso (+,+,+) de suma/3.

Cómo se construye una tabla de comportamiento. El predicado suma/3tiene, en principio, 8 usos diferentes (23, 3 argumentos en modos + o -). Pa-ra obtener la tabla de comportamiento de suma/3 basta resolver objetivosque ilustren los distintos usos.

El uso (+,+,+) es un test que comprueba que el tercer elemento de larelación es la suma de los dos primeros. El objetivo:

?- suma(s(s(c)),s(c),s(s(s(c)))).Yes

termina con éxito y da lugar al árbol de búsqueda de la figura 3. El casorecursivo va eliminando functores s/1 tanto de X como de Z. En el casobase, X se ha reducido a c y Y y Z deben ser idénticos.

El uso (+,+,-) es un generador único que calcula la suma de los dosprimeros argumentos:

?- suma(s(s(c)),s(c),Z).Z= s(s(s(c))) ;No

La figura 4 muestra un uso (+,+,-) de suma/3. El caso recursivo va eli-minando functores s/1 de X y construyendo progresivamente Z. Para ello,

Notas de clase Octubre 2007

Page 11: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.11

1) suma(s(s(c)), s(c), Z)

2) suma(s(c), s(c), Z1)

2 - {X1/s(c), Y1/s(c), Z/s(Z1)}

3) suma(c, s(c), Z2)

2 - {X2/c, Y2/s(c), Z1/s(Z2)}

4) es_natural(s(c))

1 - {Y3/s(c), Z2/s(c)}

5) es_natural(c)

4 - {X4/c}

6. éxito

{Z/s(s(s(c)))}

3 -

Figura 4: Uso (+,+,-) de suma/3.

se introducen variables intermedias Zi y se construyen sustituciones de laforma {Zi/s(Zi+1)}. El caso base unifica Y con la última variable Zn in-troducida, donde n es el número de functores s/1 de X, completando laconstrucción de Z. Esta construcción progresiva de la solución mediantevariables y sustituciones intermedias es típica de la programación lógica,por lo que es importante familiarizarse con este mecanismo.

El uso (+,-,+) se comporta como un generador único que calcula ladiferencia entre el tercer y el primer argumento:

?- suma(s(s(c)),Y,s(s(s(c)))).Y= s(c) ;No

Lo interesante de este ejemplo es que estamos usando el mismo códigopara sumar y para restar. En el estilo funcional, los argumentos de la fun-ción son siempre entradas que se deben facilitar y el valor devuelto porla función es la salida. En el estilo relacional desaparece la dependenciade las salidas respecto de las entradas. Que un parámetro sea de entradao salida depende de cuál sea su modo. El árbol de búsqueda se muestraen la figura 5. Como en el uso (+,+,+), el caso recursivo va eliminandofunctores de X y Z. Al llegar al caso base se unifica la incógnita Y con Z.

El uso (-,+,+) es similar a (+,-,+). También es un generador únicoque sirve para restar. La diferencia es que en este caso el árbol (ver figu-ra 6) contendrá una rama fallo adicional. Esto se debe a que cuando Y y

Notas de clase Octubre 2007

Page 12: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.12 Técnicas básicas de programación Prolog

1) suma(s(s(c)), Y, s(s(s(c))))

2) suma(s(c), Y, s(s(c)))

2 - {X1/s(c), Y1/Y, Z1/s(s(c))}

3) suma(c, Y, s(c))

2 - {X2/c, Y2/Y, Z2/s(c)}

4) es_natural(s(c))

1 - {Y3/s(c), Y/s(c)}

5) es_natural(c)

4 - {X4/c}

6. éxito

{Y/s(c)}

3 -

Figura 5: Uso (+,-,+) de suma/3.

1) suma(X, s(s(c)), s(s(s(c))))

2) suma(X1, s(s(c)), s(s(c)))

2 - {X/s(X1), Y1/s(s(c)), Z1/s(s(c))}

3) es_natural(s(s(c)))

1 - {X1/c, Y2/s(s(c))}

4) es_natural(s(c))

4 - {X3/s(c)}

5) es_natural(c)

4 - {X4/c}

6. éxito

{X/s(c)}

3 -

7) suma(X2, s(s(c)), s(c))

2 - {X1/s(X2), Y2/s(s(c)), Z2/s(c)}

8) suma(X3, s(s(c)), c)

fallo

2 - {X2/s(X3), Y3/s(s(c)), Z3/c}

Figura 6: Uso (-,+,+) de suma/3.

Notas de clase Octubre 2007

Page 13: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.13

1) suma(X, Y, s(s(c)))

2) es_natural(s(s(c)))

1 - {X/c, Y1/s(s(c)), Y/s(s(c))}

3) es_natural(s(c))

4 - {X2/s(c)}

4) es_natural(c)

4 - {X3/c}

5. éxito

{X/c, Y/s(s(c))}

3 -

6) suma(X1, Y, s(c))

2 - {X/s(X1), Y1/Y, Z1/s(c)}

7) es_natural(s(c))

1 - {X1/c, Y2/s(c), Y/s(c)}

8) es_natural(c)

4 - {X3/c}

9. éxito

{X/s(c), Y/s(c)}

3 -

10) suma(X2, Y, c)

2 - {X1/s(X2), Y2/Y, Z2/c}

11) es_natural(c)

1 - {X2/c, Y3/c, Y/c}

12. éxito

{X/s(s(c)), Y/c}

3 -

Figura 7: Uso (-,-,+) de suma/3.

Z unifican, se pueden aplicar tanto el caso base como el caso recursivo. Elprimero completará la construcción de X y conducirá a la solución, mien-tras que el segundo acabará fallando al reducir Z a c. Aunque ambos usossirven para restar, el uso (+,-,+) es preferible por ser más eficiente.

El uso (-,-,+) se comporta como un generador acotado que descom-pone un natural dado Z en dos sumandos X e Y:

?- suma(X,Y,s(s(c))).X= cY= s(s(c)) ;X= s(c)Y= s(c) ;X= s(s(c)) ;Y= cNo

Este caso ilustra la potencia que puede alcanzar el estilo relacional. El árbolde resolución se muestra en la figura 7. Puesto que el caso base y el re-cursivo no son excluyentes, la ejecución de este objetivo genera respuestasmúltiples. Cuando se aplica el caso base, X se instancia a c y se unifican Yy Z, una forma trivial de satisfacer la relación. El caso recursivo elimina un

Notas de clase Octubre 2007

Page 14: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.14 Técnicas básicas de programación Prolog

1) suma(s(s(c)), Y, Z)

2) suma(s(c), Y, Z1)

2 - {X1/s(c), Y1/Y, Z/s(Z1)}

3) suma(c, Y, Z2)

2 - {X2/c, Y2/Y, Z1/s(Z2)}

4) es_natural(Z2)

1 - {Y3/Z2, Y/Z2}

5. éxito

{Y/c, Z/s(s(c))}

3 - {Z2/c}

6) es_natural(X4)

4 - {Z2/s(X4)}

7. éxito

{Y/s(c), Z/s(s(s(c)))}

3 - {X4/c}

8) es_natural(X5)

...

4 - {X4/s(X5)}

Figura 8: Uso (+,-,-) de suma/3.

functor s/1 de Z, y construye progresivamente X, introduciendo variablesy sustituciones intermedias. El uso (+,-,-) es un generador no acotado:

?- suma(s(s(c)),Y,Z).Y= cZ= s(s(c)) ;Y= s(c)Z= s(s(s(c))) ;...

en Y obtenemos los naturales y en Z los naturales mayores o iguales queX. La figura 8 muestra el árbol de búsqueda. Al estar X en modo +, loscasos base y recursivo de suma/3 son excluyentes y la recursión conver-ge de forma determinista al caso base. El caso recursivo va eliminandofunctores s/1 de X y construyendo progresivamente Z. Una vez que sealcanza el caso base, se unifican la Y y la variable Zn, completando laconstrucción de Z. En los ejemplos anteriores, todas las comprobacionesde dominio es_natural/1 se han ejecutado en modo +. En este caso, comoY ha quedado libre, la comprobación de dominio es_natural se ejecuta enmodo -, comportándose como un generador no acotado. La capacidad degenerar respuestas múltiples de suma(+,-,-) proviene del uso en modo -

Notas de clase Octubre 2007

Page 15: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.15

1) suma(X, s(c), Z)

2) es_natural(s(c))

1 - {X/c, Y1/s(c), Z/s(c)}

3) es_natural(c)

4 - {X2/c}

4. éxito

{X/c, Z/s(c)}

3 -

5) suma(X1, s(c), Z1)

2 - {X/s(X1), Y1/s(c), Z/s(Z1)}

6) es_natural(s(c))

1 - {X1/c, Y2/s(c), Z1/s(c)}

7) es_natural(c)

...

4 - {X3/c}

8) suma(X2, s(c), Z2)

2 - {X1/s(X2), Y2/s(c), Z1/s(Z2)}

9) es_natural(s(c))

...

1 - {X2/c, Y3/s(c), Z2/s(c)}

10) suma(X3, s(c), Z3)

...

2 - {X2/s(X3), Y3/s(c), Z2/s(Z3)}

Figura 9: Uso (-,+,-) de suma/3.

del predicado es_natural/1. El uso (-,+,-) es similar al uso (+,-,-):

?- suma(X,s(s(c)),Z).X= cZ= s(s(c)) ;X= s(c)Z= s(s(s(c))) ;...

el árbol de resolución de la figura 9 muestra que, en este caso, se generanrespuestas múltiples porque los casos base y recursivo de suma/3 no sonexcluyentes; mientras que es_natural/1 se comporta como un test.

Finalmente, el uso (-,-,-) se comporta como un generador anómalo:

?- suma(X,Y,Z).X= cY= cZ= c ;X= cY= s(c)Z= s(c) ;...

Notas de clase Octubre 2007

Page 16: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.16 Técnicas básicas de programación Prolog

1) suma(X, Y, Z)

2) es_natural(Z)

1 - {X/c, Y1/Z, Y/Z}

3. éxito

{X/c, Y/c, Z/c}

3 - {Z/c}

4) es_natural(X2)

...

4 - {Z/s(X2)}

5) suma(X1, Y, Z1)

2 - {X/s(X1), Y1/Y, Z/s(Z1)}

6) es_natural(Z1)

...

1 - {X1/c, Y2/Z1, Y/Z1}

7) suma(X2, Y, Z2)

...

2 - {X1/s(X2), Y2/Y, Z1/s(Z2)}

Figura 10: Uso (-,-,-) de suma/3.

La semántica declarativa nos dice que este objetivo debería generar to-das las tripletas (X, Y, Z) tales que Z = X + Y. Sin embargo, la ejecucióndel objetivo —su semántica operacional— genera tripletas de la forma(0, Y, Z), con Z = 0 + Y = Y. El problema, tal y como ilustra la la fi-gura 10, es que hay más de una rama infinita. El objetivo suma(X,Y,Z)se puede resolver aplicando el caso base o el recursivo. Al aplicar el pri-mero, X se instancia a c, se unifican Y y Z, y obtenemos el subojetivoes_natural(Z), que se comporta como un generador no acotado. Puestoque la construcción del árbol se hace en profundidad, nunca se considerala posibilidad de aplicar el caso recursivo al objetivo inicial.

La figura 11 muestra la tabla de comportamiento del predicado suma/3.

Generadores, dominios y respuestas genéricas. Añadir comprobacionesde dominio a los predicados, tal como acabamos de hacer con suma/3, tieneventajas e inconvenientes.

Entre las ventajas podemos destacar que nos puede ayudar a encontrarerrores en los programas al fracasar objetivos que devolverían respuestascomputadas erróneas:

Notas de clase Octubre 2007

Page 17: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.17

suma(X,Y,Z)uso comportamiento significado(+,+,+) test Z = X + Y(+,+,-) generador único suma: Z = X + Y(+,-,+) generador único resta: Y = Z− X(-,+,+) generador único resta: X = Z−Y(-,-,+) generador acotado {(X, Y)/X ≤ Z ∧Y ≤ Z ∧ Z = X + Y}(+,-,-) generador no acotado {(Y, Z)/Y ∈ N∧ Z = X + Y}(-,+,-) generador no acotado {(X, Z)/X ∈ N∧ Z = X + Y}(-,-,-) generador anómalo X = 0, Y ∈ N, Z = Y

Figura 11: Tabla de comportamiento de suma/3 con comprobación de do-minio.

?- suma(c,a,Z).No

Además, puesto que la definición de dominio es_natural/1 puede com-portarse como un generador, el predicado suma/3 hereda esa capacidadgeneradora siempre que el segundo y tercer argumentos estén en modo -,tal como vimos al analizar el uso (+,-,-):

?- suma(s(s(c)),Y,Z).Y = cZ = s(s(c)) ;Y = s(c)Z = s(s(s(c))) ;Y = s(s(c))Z = s(s(s(s(c)))) ;...

En cuanto a los inconvenientes de incorporar una comprobación dedominio, uno muy obvio es la ineficiencia. Además, conceptualmente es-tamos clasificando como falsos objetivos que quizá deberían interpretarsecomo errores y deberían por tanto ser rechazados por el sistema. Final-mente, al retener la capacidad generadora de es_natural/1, el predicadosuma/3 pierde la posibilidad de devolver respuestas genéricas; es decir, res-puestas que contengan variables libres. Si definimos el predicado sumag/3omitiendo la comprobación de dominio en el caso base de suma/3:

sumag(c,Y,Y).sumag(s(X),Y,s(Z)) :- sumag(X,Y,Z).

para el siguiente objetivo :

?- sumag(s(s(c)),Y,Z).

Notas de clase Octubre 2007

Page 18: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.18 Técnicas básicas de programación Prolog

Z = s(s(Y)) ;No

obtenemos una respuesta genérica, pues Y queda libre. Las variables li-bres de las respuestas computadas se considera que están universalmentecuantificadas; es decir, que pueden reemplazarse por cualquier término.El objetivo pregunta a Prolog si existen Y y Z tales que Z = 2 + Y, yProlog responde que Y puede ser cualquier natural, y Z será dos unida-des superior a Y. 4

En general, prescindiremos de la comprobación de dominios, de ma-nera que sacrificaremos la corrección para mejorar la eficiencia y obtenerrespuestas genéricas.

Otras relaciones aritméticas. A continuación presentamos las definicio-nes y tablas de comportamiento de otras relaciones aritméticas típicas.

iguales(X,Y) se satisface si X = Y.Se puede definir aplicando recursión al primer argumento:

iguales(c,c).iguales(s(X),s(Y)) :- iguales(X,Y).

iguales(X,Y)uso comportamiento significado(+,+) test X = Y(+,-) generador único copia: Y = X(-,+) generador único copia: X = Y(-,-) generador no acotado {(X, Y)/X = Y ∧ X ∈ N}

Otra forma de definir iguales/2 consiste en emplear la unificaciónpara forzar la igualdad entre los argumentos, y comprobar su sintaxis enel cuerpo de la regla:

iguales(X,X) :- es_natural(X).

menor(X,Y) se satisface si X < Y.Podemos aplicar recursión sobre el primer argumento:

menor(c,Y) :- natural(Y).menor(s(X),s(Y)) :- menor(X,Y).

4En realidad Prolog responde que Y puede ser cualquier término, incluso uno que nosea un natural bien construido. Es el usuario el que interpreta que Y puede ser cualquiernatural.

Notas de clase Octubre 2007

Page 19: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.19

menor(X,Y)uso comportamiento significado(+,+) test X < Y(+,-) generador no acotado {Y/Y > X}(-,+) generador acotado {X/X < Y}(-,-) generador anómalo

Ejercicios

1. Construye la tabla de comportamiento el predicado sumag/3 y com-párala con la de suma/3.

2. ¿Qué ocurre al ejecutar el objetivo iguales(X,s(X))?

3. ¿Qué ocurre si eliminamos la comprobación es_natural(Y) en el ca-so base de menor/2? ¿Cómo afecta esto a la tabla de comportamiento?

4. Define el predicado menor_igual(X,Y), que se satisface si X ≤ Y.

minimo(X,Y,Z) se satisface si Z = mı́n(X, Y).Basta aplicar recursión simultánea sobre los dos primeros argumentos. Estopermite reducir ambos argumentos hasta que uno de ellos se haga cero:

minimo(c,Y,c) :- es_natural(Y).minimo(X,c,c) :- es_natural(X).minimo(s(X),s(Y),s(Z)) :- minimo(X,Y,Z).

La definición anterior es incorrecta. Si ambos argumentos son iguales, ob-tenemos una respuesta repetida, lo que no tiene sentido pues el mínimo dedos naturales es único. Para solucionarlo, basta considerar explícitamentelas tres posibilidades: que ambos argumentos se hagan cero simultánea-mente, o que uno de ellos se haga cero y el otro no:

minimo(c,c,c).minimo(c,s(Y),c) :- es_natural(Y).minimo(s(X),c,c) :- es_natural(X).minimo(s(X),s(Y),s(Z)) :- minimo(X,Y,Z).

Esta definición sí es correcta, pero se puede hacer más concisa agrupandolos dos primeros hechos:

minimo(c,Y,c) :- es_natural(Y).minimo(s(X),c,c) :- es_natural(X).minimo(s(X),s(Y),s(Z)) :- minimo(X,Y,Z).

Alternativamente, podemos evitar la recursión haciendo uso de las re-laciones menor/2 y menor_igual/2:

Notas de clase Octubre 2007

Page 20: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.20 Técnicas básicas de programación Prolog

minimo(X,Y,X) :- menor_igual(X,Y).minimo(X,Y,Y) :- menor(Y,X).

producto(X,Y,Z) se satisface si Z = X ∗Y.La idea se basa en la siguiente definición recursiva de producto:

Z = X ∗Y = X + X + X + · · ·+ X︸ ︷︷ ︸Y veces

= (X + X + · · ·+ X)︸ ︷︷ ︸(Y−1) veces

+X

= X ∗ (Y − 1) + X

Por lo tanto, hay que aplicar recursión sobre el segundo argumento:

producto(X,c,c):-es_natural(X).

producto(X,s(Y),Z) :-producto(X,Y,T),suma(T,X,Z).

producto(X,Y,Z)uso comportamiento significado(+,+,+) test Z = X ∗Y(+,+,-) generador único producto: Z = X ∗Y(-,+,-) generador no acotado múltiplos de Y: {(X, Z)/X ∈ N∧ Z = X ∗Y}(+,-,-) generador no acotado múltiplos de X: {(Y, Z)/Y ∈ N∧ Z = X ∗Y}(+,-,+) no funciona cociente entero: Y = Z/X y no termina(-,+,+) no funciona cociente entero: X = Z/Y y no termina(-,-,+) no funciona si Z 6= 0 entonces X = Z, Y = 1 y no termina

sino X ∈ N y Y = 0(-,-,-) generador anómalo X ∈ N, Y = 0, Z = 0

El patrón de predicado recursivo. Todos los predicados recursivos quehemos definido, y la mayoría de los que definiremos a lo largo del curso,son instancias del siguiente patrón de predicado recursivo, resolver/2:

[Caso Base]

resolver(Problema_Base,Solucion_Base) :-condiciones_base.

[Caso Recursivo]

resolver(Problema,Solucion) :-reducir(Problema, ProblemaReducido),resolver(ProblemaReducido, SolucionReducida),componer(Problema,SolucionReducida,Solucion).

Notas de clase Octubre 2007

Page 21: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.21

El predicado resolver/2 encapsula la idea fundamental de la resoluciónde problemas mediante recursión. En el caso base, la solución es inmedia-ta. En el caso recursivo, el problema original es reducido (reducir/2) aun problema menor del mismo tipo, que se resuelve recursivamente. Unavez obtenida la solución del problema reducido, ésta puede emplearse pa-ra obtener (componer/3) la solución del problema original. Al aplicar larecursión, la parte creativa corresponde a la forma en que un problemase reduce a otro menor, y la forma en la que se obtiene la solución deun problema a partir de las soluciones de problemas menores. Una elec-ción desafortunada en la forma de reducir el problema o componer lassoluciones puede conducir a programas innecesariamente complicados oincorrectos.

Algunos de los predicados anteriores parece que no se ajustan al pa-trón de predicado recursivo, pues no aparecen explícitamente los predica-dos reducir/2 y componer/3. Por ejemplo, habíamos definido el predicadosuma/3 como:

suma(c,Y,Y): es_natural(Y). [Caso Base]suma(s(X),Y,s(Z)) :- suma(X,Y,Z). [Caso Recursivo]

Lo que ocurre aquí en realidad es que la reducción del problema y lacomposición de la solución se han efectuado a través del mecanismo deunificación. El problema se reduce al pasar de la suma de s(X),Y a lasuma de X,Y, y la respuesta de obtiene a partir de Z sin más que añadir unfunctor s(Z).

Podemos aplicar el anterior patrón para definir recursivamente el pre-dicado factorial/1. En el caso base, el factorial de 0, la solución es 1. Estoqueda reflejado en el hecho:

factorial(c,s(c)).

En el caso recursivo, debemos reducir el problema pasando del factorialde n al factorial de n− 1. Esto puede hacerse por unificación, pasando delfactorial de s(X) al factorial de X:

factorial(s(X),F) :-factorial(X,T),...

Ahora debemos emplear T para componer la solución F. Si la solucióndel problema reducido (n − 1) ! es T, entonces la solución del problemaoriginal n ! es n ∗ T, de donde obtenemos:

factorial(c,s(c)).factorial(s(X),F) :-

factorial(X,T),producto(s(X),T,F).

Notas de clase Octubre 2007

Page 22: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.22 Técnicas básicas de programación Prolog

Ejercicios

1. Analizar por qué no funcionan correctamente los usos (-,+,+), (+,-,+)y (-,-,+) de producto/3.

2. Comparar la eficiencia de los usos (+,-,+) y (-,+,+) de producto/3.

3. Comprobar si la siguiente definición de producto/3:

producto(X,c,c):- es_natural(X).producto(X,s(Y),Z) :-

suma(T,X,Z),producto(X,Y,T).

es equivalente a la anterior. Estudiar su comportamiento en el uso(-,+,+) ¿Influye el orden en que aparecen los objetivos del cuerpode una regla?

4. Definir el predicado exp(X,Y,Z) que se satisface si Z = XY. Suge-rencia: plantear la recursión basándose en que XY = X ∗ X(Y−1).Recordar que 00 no está definido.

5. Definir el predicado mod(X,Y,Z) que se satisface si Z = X mod Y. Su-gerencia: plantear la recursión basándose en el siguiente invariante

X mod Y = (X −Y) mod Y si X ≥ Y.

6. Definir el precidado mcd(X,Y,Z) que se satisface si Z es el máximocomún divisor de X e Y.

7. Definir el predicado fibonacci(N,X) que se satisface si X es el N-ésimo número de Fibonacci.

8. Definir el predicado entre(X,Y,Z) que se satisface si X ≤ Z ≤ Y. Elpredicado debe funcionar al menos en los usos (+,+,+) y (+,+,-).

2. Recursión de cola y predicados iterativos

La recursión y la iteración son, desde el punto de vista de la capa-cidad expresiva, mecanismos equivalentes. Todo lo que pueda resolverserecursivamente puede resolverse iterativamente y viceversa. La diferenciafundamental entre ambos mecanismos está en la eficiencia.

Consideremos el siguiente bucle:

Notas de clase Octubre 2007

Page 23: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.23

loop guarda(x,y)S1(x,z);S2(y,z);S3(y);

end loop;

donde Si(v) representa una sentencia que accede a la variable v. Si las sen-tencias Si del cuerpo del bucle tienen tiempo de ejecución constante, O(1),entonces el coste total de ejecutar el bucle es directamente proporcional alnúmero de iteraciones, O(N). Por otro lado, si las sentencias Si no con-sumen memoria dinámica, el espacio empleado por el bucle es constante,O(1), independientemente del número de iteraciones.

El siguiente procedimiento recursivo p es equivalente al anterior bucle:

procedure p(x,y,z)begin

if guarda(x,y) thenS1(x,z);S2(y,z);S3(y);p(x,y,z);

end;end;

donde las variables del bucle han sido reemplazadas por parámetros delprocedimiento. Desde el punto de vista del tiempo, el procedimiento p esO(N), al igual que el bucle original. Sin embargo, desde el punto de vistadel espacio, el procedimiento p es O(N); es decir, el espacio empleadoes directamente proporcional al número de veces que se ejecuta. Esto sedebe a que cada invocación de p requiere crear un registro de activaciónsobre la pila. Tal registro contendrá, al menos, la dirección de retorno, losparámetros formales y las variables locales del procedimiento.

Es posible optimizar la ejecución del procedimiento p de manera queel coste en espacio permanezca constante, O(1). Para ello, basta notar quela llamada recursiva es la última sentencia del cuerpo de p. A este tipode recursión se le llama recursión de cola. Si un procedimiento es recursivode cola, entonces al efectuar la llamada recursiva su registro de activaciónpuede ser reutilizado; pues a la vuelta no lo va a necesitar (la llamada re-cursiva es la última sentencia). invocación de un procedimiento recursivode cola, basta crear un solo registro de activación para la primera invoca-ción, y que las posteriores invocaciones recursivas lo vayan reescribiendo.Esta optimización permite ejecutar un procedimiento recursivo con el mis-mo coste, en espacio y tiempo, que un bucle.5

5La optimización de la recursión de cola es un caso especial de la optimización de

Notas de clase Octubre 2007

Page 24: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.24 Técnicas básicas de programación Prolog

La mayoría de los lenguajes declarativos (lógicos y funcionales) opti-mizan la recursión de cola. En el caso de Prolog, para que un predicadorecursivo de cola sea optimizable, es necesario que el predicado sea ite-rativo. Un predicado es iterativo si es recursivo de cola y su ejecución esdeterminista; es decir, en su ejecución no se introducen alternativas. Engeneral, la última llamada —recursiva o no— se puede optimizar si enel momento de invocación no quedan alternativas pendientes de explorar.Ilustraremos este concepto mediante varios ejemplos.

Ejemplo 1. El predicado p/0:

p :- q(X), p.

q(a).q(b).

es recursivo de cola pero no es iterativo. La ejecución de q(X) introduceuna alternativa en el árbol de búsqueda, de manera que al efectuar lallamada recursiva quedan alternativas por explorar.

Ejemplo 2. El predicado p/0:

p :- q, p.p :- r, p.

q.r.

es recursivo de cola pero no es iterativo. Esto se debe a que la ejecuciónde p introduce una alternativa en el árbol de búsqueda, según se apliquela primera o segunda definición.

Ejemplo 3. El predicado p/0:

p :- q(a), p.

q(a).q(b).

es recursivo de cola e iterativo, pues su ejecución no introduce alternativasen el árbol de búsqueda.

Prolog es capaz de aplicar la optimización de la última llamada entiempo de ejecución, tal y como ilustra el siguiente ejemplo.

la última llamada (last call optimization). Si la última sentencia de un procedimiento p esuna invocación a un procedimiento q, entonces el registro de pila de q puede crearsereescribiendo el registro de p.

Notas de clase Octubre 2007

Page 25: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.25

Ejemplo 4. El predicado p/1:

p(X) :- q(X), p(X).

q(a).q(b).

es recursivo de cola e iterativo. Al ejecutar un objetivo p(Z) aparecen dosalternativas para resolver q(Z) que instancian Z a a y b, respectivamente.Cada uno de estos subárboles no contiene más alternativas, por lo que pse puede ejecutar con a lo más tres registros de activación.

Puesto que los predicados recursivos de cola pueden ser ejecutadosmás eficientemente, parece natural plantearse cómo escribir un predicadorecursivo de cola. Estos predicados corresponden de manera directa a bu-cles donde la solución se obtiene a través de una variable que actúa comoacumulador:

Acum:= inicial;loop guarda(...)

...actualizar(Acum);...

end loop;

Al finalizar el bucle, la variable Acum contiene la solución. Para imitar estosbucles mediante recursión, basta escribir un predicado recursivo de colaque reciba como parámetros el problema a resolver y la variable acumula-dora:

procedure rec_de_cola(Problema,Acum)begin

if guarda(...) thenreducir(Problema,ProblemaReducido);actualizar(Acum);rec_de_cola(ProblemaReducido,Acum)

end;end;

El parámetro Acum debe pasarse por referencia. Antes de invocar al proce-dimiento rec_de_cola, se debe inicializar Acum a inicial.:

Acum:= inicial;p(Problema, Acum);

Al finalizar la ejecución, el parámetro Acum contiene la solución del pro-blema original.

Notas de clase Octubre 2007

Page 26: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.26 Técnicas básicas de programación Prolog

Para codificar el anterior procedimiento en Prolog, se deben tener encuenta varios detalles. En primer lugar, Prolog carece de sentencia deselección (if ... then). Esto no tiene la menor importancia, pues bastaintroducir una regla —o un hecho— para el caso base y una regla recur-siva para el caso recursivo, como hicimos en los anteriores ejemplos derecursión. Por otro lado, en Prolog no es posible actualizar la variableacumuladora (Acum). Lo que haremos será reemplazar la sentencia:

actualizar(Acum);

que presupone una variable imperativa y por tanto actualizable, por lasentencia:

actualizar(Acum,Nuevo_Acum);

donde se introduce una nueva variable, Nuevo_Acum, que contiene el nue-vo valor del acumulador. Por otro lado, Prolog carece de parámetros porreferencia tal y como se definen en el modelo imperativo. Lo que haremosserá introducir una variable adicional, Sol, que permanecerá libre (sin ins-tanciar) y se propagará a través de las llamadas recursivas hasta alcanzarel caso base, instanciándose entonces al valor del acumulador; es decir, lasolución del problema. Por lo tanto, el patrón de un predicado recursivode cola es:

[Caso Base]rec_de_cola(Problema_Base,Sol,Sol) :-

condiciones_base.

[Caso Recursivo]rec_de_cola(Problema,Sol,Acum) :-

reducir(Problema,ProblemaReducido),actualizar(Acum,NuevoAcum),rec_de_cola(ProblemaReducido,Sol,NuevoAcum).

Debe tenerse en cuenta que el procedimiento rec_de_cola/3 se empleasiempre en uso (+,-,+); es decir, conocemos el problema y el valor ac-tual del acumulador, y desconocemos la solución. El usuario debería invo-car al procedimiento rec_de_cola/3 inicializando el acumulador al valorinicial, que debe ser la solución del problema en el caso base. Para ase-gurar el empleo correcto de rec_de_cola/3, suele ser útil introducir unpredicado intermedio que libere al usuario de esta obligación:

resolver(Problema,Solucion) :-rec_de_cola(Problema,Solucion,Solucion_Base).

La parte creativa de la aplicación de este patrón corresponde a la reduc-ción del problema y, sobre todo, a la actualización del acumulador. Al-gunos predicados recursivos no de cola pueden transformarse de manera

Notas de clase Octubre 2007

Page 27: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.27

automática en predicados recursivos de cola equivalentes. El estudio deesta transformación automática, basada en la técnica plegar/desplegar, seencuentra fuera del alcance de este curso.

Ejemplo: factorial con recursión de cola. Podemos aplicar el patrón paracalcular el factorial empleando recursión de cola. Comenzaremos introdu-ciendo un predicado que juegue el papel de interfaz:

factorial(N,F) :-factorial_cola(N,F,s(c)).

El acumulador se ha inicializado a s(c), la solución del caso base. En elcaso base de factorial_cola/3, el acumulador debe contener la solución:

factorial_cola(c,F,F).

Por otro lado, la regla recursiva debe seguir el esquema:

factorial_cola(s(X),F,Acum) :-reducir(...),actualizar(Acum,Nuevo_Acum),factorial_cola(...,F,Nuevo_Acum).

Hasta aquí, todo ha sido mecánico. Reducir el problema es fácil: bastaconsiderar el factorial de X, lo que podemos hacer por simple unificación:

factorial_cola(s(X),F,Acum) :-actualizar(Acum,Nuevo_Acum),factorial_cola(X,F,Nuevo_Acum).

Todo lo que queda es actualizar el acumulador. Aquí debe quedar refleja-do de alguna manera que estamos calculando el factorial de s(X); es decir,debemos combinar Acum con s(X) de alguna manera. En esta ocasión, bas-ta con multiplicar s(X) con Acum para obtener el nuevo acumulador, dedonde finalmente:

factorial(N,F) :-factorial_cola(N,F,s(c)).

factorial_cola(c,F,F).

factorial_cola(s(X),F,Acum) :-producto(s(X),Acum,Nuevo_Acum),factorial_cola(X,F,Nuevo_Acum).

El predicado factorial es recursivo de cola y en el momento de efectuarla llamada recursiva no quedan alternativas pendientes, por lo que es ite-rativo. Sin embargo, el consumo de memoria es proporcional al tamaño

Notas de clase Octubre 2007

Page 28: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.28 Técnicas básicas de programación Prolog

del problema, pues producto/3, tal y como lo definimos antes, no tienerecursión de cola y por lo tanto tiene coste O(N) en espacio. Para solu-cionar esto, basta escribir una versión recursiva de cola de producto/3.Aplicando el patrón obtenemos:

producto(X,Y,Z) :-producto_cola(X,Y,Z,c).

producto_cola(c,Y,Z,Z).

producto_cola(s(X),Y,Z,Acum) :-suma(Y,Acum,Nuevo_Acum),producto_cola(X,Y,Z,Nuevo_Acum).

Puesto que suma/3 es recursivo de cola y, además, en el uso +,+,- es itera-tivo, producto_cola/4 es iterativo, y también lo será factorial_cola/3.

Ejercicios

1. Define una versión recursiva de cola de exp/3. ¿Es iterativa tu solu-ción?

2. Define un predicado recursivo Fibonacci/2 para calcular el n-ésimonúmero de Fibonacci. Escribe una versión recursiva de cola. Suge-rencia: Puesto que Fibonacci/2 contiene dos llamadas recursivas yla recursión de cola sólo admite una llamada, será necesario utilizardos acumuladores.

3. El paradigma generar/comprobar

Existen problemas para los que es muy difícil —sino imposible— en-contrar algoritmos deterministas que los resuelvan. Como ejemplos de es-tos problemas cabe citar, entre otros, el problema de las n-reinas, el reco-rrido del caballo, las parejas estables, diversas variantes del problema dela mochila, etc. Al tratar resolver este tipo de problemas, nos encontramosen situaciones en las que no sabemos qué alternativa de entre las posibleses la adecuada; es decir, cuál es el siguiente paso que debe dar el algoritmopara llevarnos a una solución. Para resolver estos problemas se suele em-plear una estrategia de búsqueda que proceda, mediante prueba y error,a explorar el espacio de posibilidades. La estrategia más habitual es lavuelta atrás (backtracking), pero existen otras posibilidades como la bús-queda en anchura o la ramificación y poda. Implementar estas estrategiasen lenguajes imperativos es engorroso y propenso a errores. Por el con-trario, Prolog aporta de forma automática toda la maquinaria necesariapara implementar la búsqueda en profundidad.

Notas de clase Octubre 2007

Page 29: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.29

Ya hemos visto varios ejemplos de predicados Prolog que se compor-tan como generadores. Los generadores pueden aplicarse para resolverproblemas que deben resolverse mediante búsquedas. La idea se basa enutilizar los generadores y el retroceso de Prolog para implementar el me-canismo de resolución de problemas por prueba y error (vuelta atrás). Seemplea un generador para generar un candidato a solución al problemay después se comprueba si, efectivamente, el candidato es una solución.Esta estrategia queda recogida en el siguiente patrón de predicado:

resolver(Problema,Solucion) :-generar(Problema,Candidato),comprobar(Problema,Candidato),Solucion = Candidato.

donde suponemos que Problema está en modo + y Solucion está en mo-do -. El funcionamiento es muy simple. Primero, el predicado generar/2genera un candidato a solución. Después, el predicado comprobar/2 com-prueba si en efecto Candidato es solución del problema propuesto. Si elobjetivo comprobar(Problema,Candidato) fracasa, se retrocede y se prue-ba con el siguiente Candidato generado por generar/2. Si, por el contra-rio, el objetivo comprobar(Problema,Candidato) tiene éxito, se unificanSolucion y Candidato.6 El usuario puede solicitar que se encuentren mássoluciones sin más que pulsar ’;’ en el entorno Prolog. Si existen variassoluciones, se encontrarán todas en el orden de generación. Comparadocon el enfoque imperativo, el enfoque lógico permite al programador con-centrarse en la especificación del problema a resolver sin preocuparse pordetalles de implementación del método de resolución (la búsqueda).

La clave del éxito de esta estrategia reside, obviamente, en el genera-dor. Debe evitarse emplear generadores no acotados, pues en tal caso elpredicado resolver/2 no termina. Aún peor sería emplear un generadoranómalo, pues no recorrería completo el espacio de candidatos. Lo ideales encontrar un generador acotado que genere un conjunto de candidatosque incluya todas las soluciones y sea lo más pequeño posible. Por otrolado, es corriente que un problema requiera emplear varios generadores yuna comprobación compuesta de varias condiciones. En tales casos, con-viene entrelazar las fases de generación y comprobación, comenzando porlos generadores y las condiciones más restrictivas.

A continuación se incluyen un par de ejemplos que ilustran la aplica-ción del paradigma generar/comprobar.

Ejemplo: descomposición de un natural en suma de pares. Dado unnatural N, queremos encontrar dos naturales X e Y tales que N = X +

6El predicado predefinido =/2 tiene éxito si sus argumentos unifican. Puesto queSolucion es una variable libre, Solucion = Candidato tendrá éxito, instanciando Solucional valor de Candidato.

Notas de clase Octubre 2007

Page 30: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.30 Técnicas básicas de programación Prolog

Y, siendo X e Y pares. Obviamente, este problema tiene solución algo-rítmica simple. Sin embargo, lo resolveremos aplicando la técnica gene-rar/comprobar.

La solución del problema está formada por un par de números, (X, Y).Definiremos un predicado en_pares(N,X,Y) que, en uso (+,-,-), gene-re por retroceso todas las soluciones posibles y se detenga. Una primeraaproximación puede ser:

en_pares(N,X,Y) :-es_par(X), % generador no acotadoes_par(Y), % generador no acotadosuma(X,Y,N). % comprobación

El problema aquí es que los objetivos es_par(X) y es_par(Y) se comportancomo generadores no acotados. Esto significa que X queda instanciado ac, mientras que Y se va instanciando sucesivamente a c, s(c), s(s(c)),s(s(s(c))). . . Si N es par, en_pares/3 genera una solución (X=c,Y=N) y notermina.

Podemos reemplazar los generadores no acotados (es_par/1) por ge-neradores acotados (menor_ig/2), aprovechando que X ≤ N y Y ≤ N:

en_pares(N,X,Y) :-menor_ig(X,N), % generador acotadomenor_ig(Y,N), % generador acotadosuma(X,Y,N), % comprobaciónes_par(X),es_par(Y).

Esta solución es correcta pero puede ser mejorada. Una vez que hemosgenerado X no es necesario generar un Y arbitrario, pues sabemos queY = N − X. Esto permite determinar Y de manera exacta:

en_pares(N,X,Y) :-menor_ig(X,N), % generador acotadoes_par(X), % comprobaciónsuma(X,Y,N), % generador unicoes_par(Y). % comprobación

De nuevo, aunque la solución es correcta, es mejorable. Aprovechando lacapacidad generadora de suma/3, los valores de X e Y pueden generarsesimultáneamente de manera que satisfagan una de las condiciones delproblema (X + Y = N).

en_pares(N,X,Y) :-suma(X,Y,N), % generador acotadoes_par(X), % comprobaciónes_par(Y).

Notas de clase Octubre 2007

Page 31: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.31

Comparando las soluciones anteriores, vemos que el predicado suma/3asume diferentes papeles (test, generador) según el uso que se haga delmismo.

Finalmente, la solución puede ser mejorada si introducimos una com-probación sobre el dato conocido, N:

en_pares(N,X,Y) :-es_par(N), % comprobacionsuma(X,Y,N), % generador acotadoes_par(X). % comprobación

Si N y X son pares y X + Y = N, entonces Y tiene que ser necesariamentepar.

Ejemplo: coloreado de un mapa. El problema del coloreado del mapaconsiste en asignar a cada una de las regiones de un mapa un color, demanera que las regiones fronterizas tengan asignados colores diferentesque permitan distinguirlas con facilidad.7 Por ejemplo, para el mapa dela figura 12, las regiones A y B deben tener asignados colores distintos,mientras que las regiones A y E pueden compartir el color.

Definiremos un predicado colorear/5 que, aplicando el paradigmagenerar/comprobar, genere todos los posibles coloreados del mapa de lafigura 12. El patrón a seguir es:

colorear(A,B,C,D,E) :-generar(A,B,C,D,E),comprobar(A,B,C,D,E).

donde cada variable debe instanciarse al color que le corresponda a laregión del mismo nombre.

Supondremos que tenemos disponibles tres colores,8 que representare-mos por los átomos rojo, azul, y verde. El predicado color/1:

color(rojo).color(azul).color(verde).

7Dado un mapa, se denomina número cromático al número mínimo de colores que debenemplearse para colorearlo de forma que las regiones fronterizas tengan colores distintos.La conjetura de Heawood acota el valor máximo de este número cromático, según el tipode figura sobre la que esté inscrito el mapa (plano, esfera, toro, . . . ) En particular, puededemostrarse que cualquier mapa planar tiene un número cromático menor o igual a 4;es decir, puede colorearse con a lo más cuatro colores. Este resultado se conoce comoel teorema de los cuatro colores, y fue demostrado por Appel y Haken en 1977. En 1996,Robertson, Sanders, Seymour y Thomas presentaron una demostración alternativa. Ambasdemostraciones requieren la ayuda de una computadora para analizar un gran número decasos, razón por la que algunos matemáticos las rechazan.

8Es fácil comprobar que el número cromático del mapa de la figura 12 es 3.

Notas de clase Octubre 2007

Page 32: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.32 Técnicas básicas de programación Prolog

A

D

B

C

E

Figura 12: Un mapa con cinco regiones y número cromático tres.

es una definición de dominio o tipo que enumera los colores disponibles.En el uso +, color(+) se comporta como un test que comprueba si su ar-gumento es un color válido. En el uso -, color(-) se comporta como ungenerador acotado que devuelve, por retroceso, todos los colores disponi-bles. Esto es precisamente lo que necesitamos para resolver este problema.

Una primera solución al coloreado del mapa es:

colorear(A,B,C,D,E) :-color(A), color(B), % generarcolor(C), color(D), color(E),A\==B, A\==C, A\==D, % comprobarB\==C, B\==E,C\==D, C\==E,D\==E.

Esta solución es correcta, pero muy ineficiente, pues se generan hasta35 = 243 coloreados posibles, la mayoría de ellos incorrectos. Considere-mos el comportamiento de colorear/5 más detenidamente. La primerasolución de color(A) asocia a A el color rojo. Igualmente, la primera so-lución a color(B) instancia B a rojo, lo que es incorrecto, pues A y B sonregiones fronterizas. Independientemente de cómo se complete el colo-reado de C, D y E, estos coloreados deben ser rechazados. Esto significaque se van a generar 33 = 27 coloreados incorrectos. Si consideramos queesta situación se va a repetir cada vez que A y B compartan color, tene-mos 34 = 81 coloreados incorrectos; es decir, el 33,33 % de los coloreadosgenerados son incorrectos porque A y B comparten color. Para evitar es-ta situación, podemos comparar los colores asignados a A y B tan prontocomo sea posible. Esta anticipación de la comprobación puede aplicarse,obviamente, a cualquier par de regiones fronterizas: tan pronto como asig-nemos color a una región, comprobaremos que es diferente del que tienenasignado sus regiones fronterizas ya coloreadas. Esto nos conduce a lasiguiente solución:

colorear(A,B,C,D,E) :-color(A), color(B), % generación/comprobación

Notas de clase Octubre 2007

Page 33: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

Técnicas básicas de programación Prolog T2.33

A\==B, % entralazadacolor(C),C\==A, C\==B,color(D),D\==A, D\==C,color(E),E\=B, E\==C, E\==D.

La idea consiste en entrelazar los procesos de generación y comprobación,para comprobar condiciones parciales tan pronto como sea posible.

La solución anterior puede mejorarse un poco más. En primer lugar,si observamos el mapa, veremos que la región C es la que más vecinostiene. Por lo tanto, interesa colorearla lo antes posible, ya que introducela restricción más fuerte. Por otro lado, conviene colorear preferentementeregiones que sean fronterizas de las ya coloreadas, pues su coloreado estámás restringido. Aplicando estas mejoras, obtenemos la solución definiti-va:

colorear(A,B,C,D,E) :-color(C), color(A), % generación/comprobaciónA\==C, % entrelazadacolor(B), % orden más restrictivoB\==A, B\==C,color(D),D\==A, D\==C,color(E),E\=B, E\==C, E\==D.

Ejercicios

1. Aplicando el paradigma generar/comprobar, define un predicado

entre(I,J,K)

que se satisface si I ≤ K ≤ J. Asegúrate que en el uso (+,+,-) secomporte como un generador acotado de los números entre I y J,como muestra el siguiente ejemplo:

?- entre(s(s(c)), s(s(s(s(c)))),K).K= s(s(c)) ;K= s(s(s(c))) ;K= s(s(s(s(c)))) ;No

2. Aplicando el paradigma generar/comprobar, define un predicado

Notas de clase Octubre 2007

Page 34: Técnicas básicas de programación Prolog - lcc.uma.eslopez/progdec/prolog/apuntes/02-basicas/tecnicas... · Técnicas básicas de programación Prolog Programación Declarativa

T2.34 Técnicas básicas de programación Prolog

coc_res(Dd,Ds,C,R)

que, dados Dd y Dv, devuelva en C y R el cociente y resto de la divisiónentera Dd/Ds. Sugerencia: emplea como condición

(Dd = C ∗ Ds + R) ∧ (R < Ds)

Notas de clase Octubre 2007