next up previous contents
Next: Ejercicios Up: Programación en lógica Previous: Programación en lógica   Contents


Introducción a la programación en lógica

Abstract:

En este capítulo se realiza una introducción a la programación en lógica. La presentación, que será informal, se basará en el concepto de relación matemática, y apelará a los conocimientos previos del lector en los aspectos de cálculo y evaluación para lenguajes tradicionales.

El ejemplo que se usará, definir relaciones de parentezco familiar, constituye el caso clásico para presentar el tema. Se definirán las relaciones por enumeración, para luego, mediante el concepto de recursión, introducir relaciones más complejas.

En la programación imperativa tradicional el usuario diseña sus algoritmos en términos de transiciones de estados de una máquina ficticia que corresponde al lenguaje que se esté utilizando. Así un programador Pascal utiliza los constructores del mismo (asignaciones, estructuras de control, etc.) para indicar a una supuesta máquina Pascal como van evolucionando sus estados a fin de alcanzar una situación final en la que puedan identificarse los resultados deseados.

Desde otro punto de vista un programa tradicional puede interpretarse como una función, que a partir de un estado inicial de la máquina ya mencionada, define los valores que corresponden al estado final de la misma luego de la evolución del algoritmo.

Por su parte un programa en lógica permite definir relaciones sobre ciertos dominios dados. Dicha definición puede realizarse por extensión, es decir enumerando todas las tuplas del producto cartesiano, o mediante una definición recursiva.

Por ejemplo:

        PADRE ( juan,  raquel ).
        PADRE ( luis,  jorge ).
        PADRE ( juan,  mariana ).
        PADRE ( jorge, diego ).
        PADRE ( jorge, noel ).
        PADRE ( luis,  ricardo ).

corresponde a una definición parcial de una relación llamada PADRE tal que PADRE está contenida en el producto cartesiano NOMBRES$ \times$NOMBRES, siendo NOMBRES un dominio que contiene nombres de personas.

En la definición presentada se conviene en que el nombre que aparece en el primer argumento corresponde al padre de la persona cuyo nombre aparece en el segundo argumento. Se podría haber convenido la otra alternativa posible. Lo que es fundamental destacar es que cualquiera sea la interpretación que se elija, la misma no aparecerá en el programa, ya que en él sólo presenta los elementos sintácticos del problema, dejando la semántica a la interpretación del usuario. Esta característica se pondrá en evidencia cuando en el capítulo siguiente se considere la ejecución de un programa en lógica como una derivación sintáctica en una teoría axiomática dada.

Considerando entonces el programa el ejemplo como la definición de la relación PADRE, es posible ahora operar con la misma, lo que corresponderá a interrogar la relación. Las operaciones que se pueden realizar son las clásicas: restricciones (subconjuntos), proyecciones sobre dominios, ambas para definir nuevas relaciones, y preguntas de pertenencia. La forma de exresar dichas operaciones se realiza en programación en lógica mediante el uso de variables (que se notarán con letras mayúsculas), las que ubicadas en alguno, o varios, de los argumentos de la relación, indicarán la proyección deseada. Así por ejemplo, si se desea conocer cuáles son los hijos de Juan, dicha información corresponde a una relación unaria, que se obtiene de la relación inicial, restringiéndola para aquellas tuplas que tengan juan en el primer argumento, y proyectando luego por el segundo. Eso se expresa en programación lógica de la siguiente forma:

        <- PADRE ( juan,  X ).

a lo que un sistema que implementa la programación en lógica respopnderá:

        X = raquel.
        X = mariana.

Esta respuesta corresponde a una una nueva relación, en este caso sobre un solo dominio, y lo que se presenta es la enumeración de las tuplas de la misma.

Se pudiera también estar interesado en conocer quién es el padre de Ricardo, lo que se expresará, en forma simétrica a la anterior:

        <- PADRE ( Y, ricardo ).

obteniéndose como respuesta:

        Y = luis.

De las dos preguntas presentadas aparece una de las características más importantes de la programación en lógica. Debido a su aspecto relacional, no se introducen direcciones, en el sentido clásico de datos y resultados. Un mismo programa, como el formado por el conjunto de las definiciones de la relación PADRE, permite conocer de quién es padre una persona (primer argumento como dato y segundo como resultado), como quién es el padre de una persona dada (segundo argumento como dato y primero como resultado). En la programación tradicional un cambio de dirección dato-resultado implicaría la reformulación del programa. Este aspecto de la reversibilidad de la programación en lógica, será presentado con más detalle en el capítulo 4.

Puede observarse de los ejemplos vistos, que en cada interrogación, u operación, de la relación inicial, la presencia de constantes como argumentos indica una restricción de la relación, y la presencia de variables expresa el interés de realizar alguna proyección. Generalizando entonces, tenemos que:

        <- PADRE ( luis, jorge ).

corresponde a una restricción que da como resultado una sola tupla (en este caso estamos interrogando la pertenencia de la tupla en cuestión a la relación original).

La pregunta:

        <- PADRE ( X, Y ).

corresponde a la proyección de la relación en todos sus dominios (en este caso el interés es en conocer todas las tuplas de la relación).

Es posible complementar la definición de la relación PADRE, con la definición de una nueva relación:

        MADRE ( raquel,  diego ).
        MADRE ( lucia,   mariana ).
        MADRE ( raquel,  noel ).
        MADRE ( carmen,  ricardo ).
        MADRE ( carmen,  jorge ).
        MADRE ( lucia,   raquel ).
        MADRE ( mariana, alejandra ).
        MADRE ( mariana, german ).

Las variables, que fueron utilizadas en las interrogaciones de las relaciones, permiten a su vez la definición de nuevas relaciones, en función de relaciones existentes. Si por ejemplo se desea definir la relación ABUELO_PATERNO, en función de las relaciones presentadas, se puede escribir:

        ABUELO_PATERNO(X,Y) <- PADRE(X,Z), PADRE(Z,Y).

Esta cláusula puede leerse de la forma siguiente:

«una persona X es el abuelo paterno de una persona Y, si existe una tercera persona Z tal que X es el padre de esa Z, y esa misma persona Z es el padre de Y

La nueva relación ABUELO_PATERNO, puede ahora ser operada (o interrogada) como las relaciones definidas por extensión. De esta manera la pregunta:

        <- ABUELO_PATERNO ( W, diego ).

generará como respuesta:

        W = luis.

Para obtener dicho resultado el sistema debió descubrir que existe una persona Jorge tal que PADRE(jorge,diego) está definida, para luego determinar que PADRE(luis,jorge) también lo está. Ese valor de luis es el que resuelve el problema, y por ello el sistema lo escribe como resultado. Es de hacer notar que las variables que aparecen en cada cláusula sólo tienen validez en la misma cláusula, es decir, utilizando nociones de los lenguajes de programación imperativa, que el alcance de una variable es la cláusula donde aparece. Es por ello que la W de la pregunta, se conectará con la X de la definición de la relación, mediante un mecanismo similar al pasaje de parámetros de los lenguajes tradicionales.

Una nueva pregunta:

        <- ABUELO_PATERNO ( W, R ).

generará a su vez como respuesta:

        W = luis, R = diego.
        W = luis, R = noel.

Esta respuesta corresponde a la enumeración de toda la relación ABUELO_PATERNO, en función de la información disponible; lógicamente que el agregado de nuevas tuplas a la relación PADRE inicial podrá modificar a ABUELO_PATERNO. La pregunta ABUELO_PATERNO(W,diego) corresponde a una restricción de la respuesta anterior.

En forma similar a lo ya visto se puede definir una nuneva relación:

        ABUELO_MATERNO(X,Y) <- PADRE(X,Z), MADRE(Z,Y).

Un nuevo paso de generalización puede ser ahora dado. En base a las relaciones definidas hasta el presente es posible ahora incluir una nueva definición, que corresponda al concepto de abuelo en forma genérica. El abuelo de una persona es el abuelo paterno o el abuelo materno. Esta situación se expresa en programación lógica de la forma siguiente:

        ABUELO(X,Y) <- ABUELO_PATERNO(X,Y).
        ABUELO(X,Y) <- ABUELO_MATERNO(X,Y).

donde al tener dos reglas para la definición de abuelo, estamos indicando que podemos utilizar una o la otra.

Ante la pregunta:

        <- ABUELO( X, noel ).

el sistema responderá:

        X = luis.
        X = juan.

Para elaborar dicha respuesta, el sistema ha debido determinar mediante la relación ABUELO_PATERNO el valor X=luis, y con la relación ABUELO_MATERNO el valor X=juan.

Con las herramientas ya vistas es posible continuar definiendo relaciones que correspondan a grados directos de parentesco, como por ejemplo:

        HERMANO(X,Y) <- PADRE(Z,X), PADRE(Z,Y).
        HERMANO(X,Y) <- MADRE(Z,X), MADRE(Z,Y).

        TIO(X,Y) <- PADRE(Z,X), HERMANO(X,Z).
        TIO(X,Y) <- MADRE(Z,X), HERMANO(X,Z).

        PRIMO(X,Y) <- TIO(Z,X), PADRE(Z,Y).
        PRIMO(X,Y) <- TIO(Z,X), MADRE(Z,Y).



Subsections
next up previous contents
Next: Ejercicios Up: Programación en lógica Previous: Programación en lógica   Contents
Cesar Ballardini
2003-10-14
Hosted by www.Geocities.ws

1