PROGRAMACIÓN LÓGICA EN VISUAL PROLOG (PARTE I) M. EN D. D. Bibiana Pérez Castillo Pérez
En Prolog, las soluciones se alcanzan deduciendo lógicamente una cosa de algo alg o ya conocido. conocido.
Un programa Prolog no es una secuencia de acciones, sino una colección de hechos, junto con la secuencia de reglas para extraer conclusiones a partir de los hechos.
Prolog es un lenguaje declarativo, lo que significa que su escritura esta más cercana al lenguaje natural .
Lenguaje Natural
Lógica de Predicados
Un coche es divertido.
divertido (coche).
Una rosa es roja
roja (rosa).
A Juan Juan le gusta un coche coche si es divertido
gusta (juan, coche) _si_ divertido(coche).
En un programa Prolog, se definen objetos y relaciones, luego defina cuando dichas relaciones son ciertas. Juan tiene tiene un perro. perro. Esta sentencia muestra una relación entre los objetos “Juan” y “perro”; la relación es “tiene”. Una posible regla para definir cuando la sentencia anterior es cierta es: Juan tiene tiene un perro si el perro es un pastor alemán.
Hechos: lo que es conocido.
En Prolog, una relación entre objetos recibe el nombre de predicado. En el lenguaje natural, una relación se expresa mediante sentencias. Juan tiene un perro. Elena tiene un coche. Juan tiene un libro.
Por el contrario, en Prolog usamos un símbolo de predicado que contiene (entre paréntesis) los distintos objetos a los que afecta dicho predicado. tiene (juan, perro). tiene (elena, coche). tiene (juan, libro).
Reglas: lo que se puede inferir de los hechos.
Las reglas nos permiten deducir hechos a partir de otros hechos. Pedro tiene todo lo que tiene Juan. Ana es rubia si lo es Elena. Y de los hechos anteriores, podemos deducir los nuevos hechos: Pedro tiene un perro. Pedro tiene un libro. Ana es rubia.
Las reglas anteriores se codificarían en Prolog tiene (pedro, Algo) : - tiene (juan, Algo). rubia(ana) :- rubia (elena).
El símbolo “ : - “ se pronuncia simplemente “si” o “se define como”; y sirve para separar las dos partes de la regla: la cabeza y el cuerpo.
Una vez que Prolog dispone de una serie de hechos y reglas, podemos proceder a realizar consultas al sistema. Tiene Juan un perro?
Que, codificado en Prolog, es: tiene(juan, perro).
Dada esta consulta, Prolog responde: yes
Ya que existe un hecho que dice exactamente eso. De la misma forma, podemos preguntar: Que tiene Juan? que, en la sintaxis de Prolog, es: tiene(juan, Que). Se puede observar que “ juan” comienza en minúscula, mientras que “ Que” comienza en mayúscula. El motivo es que “ juan” es un objeto constante, mientras que “ Que” representa una variable.
Los hechos no sólo pueden expresar relaciones entre objetos, sino también propiedades de los mismos. alto (juan). rubia (elena).
Prolog siempre responde a las consultas buscando hechos o reglas adecuados desde el principio del programa hasta el final.
El uso de variables en Prolog nos permite escribir sentencias de carácter general. tiene (pedro, Algo) : - tiene (juan, Algo).
Nota: En Prolog, las variables siempre comienzan con una letra mayúsculas o con subrayado.
En Prolog, existen únicamente dos tipos de “sentencias”: hechos y reglas , ambos tipos de sentencias se conocen como cláusulas. El núcleo de un programa Prolog está compuesto de una secuencia de cláusulas.
Un hecho, representa una instancia simple de una
propiedad de un objeto o una relación entre objetos. Prolog no necesita mirar en ningún otro sitio para confirmar la validez de un hecho. Además, los hechos pueden utilizarse para realizar deducciones (mediante reglas).
Las reglas, sirven para describir lo que se puede
deducir a partir de otra información. Es decir, una regla indica que una propiedad ( o relación ) es cierta si ciertas propiedades ( o relaciones ) son ciertas.
Por ejemplo: Considerando la frase en lenguaje natural: Diana es vegetariana y solo come lo que su doctor le ha dicho que coma. Dado un menú y la regla anterior es posible concluir si Diana puede ordenar un determinado item del menú o no. Esta regla se podría escribir en Prolog como: diana_puede_comer(Item) : vegetal (Item), en_lista_doctor (Item)
padre_de (carlos, marcos). padre_de (tomas, diana). madre_de (elena, marcos). Y si queremos escribir una regla que defina la relación pariente_de(Persona1, Persona2) de forma que sea cierta si Persona1 es padre de Persona2, o bien Persona1 es la madre de Persona2. pariente_de (Persona1,Persona2): padre_de (Persona1, Persona2) ; madre_de (Persona1, Persona2).
Otro ejemplo seria, tomando la frase “una persona puede comprar un coche si la persona le gusta el coche y el coche esta en venta” puede_comprar (Nombre, Modelo): persona (Nombre), coche (Modelo), gusta (Nombre, Modelo), en_venta(Modelo).
Al nombre de una relación lo llamamos predicado, mientras que los objetos que relaciona se llaman argumentos . pred (integer, symbol) persona (nombre, apellido, edad) correr inserta_modo Un predicado persona se podría usar en una consulta del tipo persona (Nombre, perez, 22)
Sin embargo, ¿para qué puede servir un predicado sin argumentos?, su utilidad puede consistir en provocar la ejecución del cuerpo de una regla (cuya cabeza sea, por ejemplo correr ), o bien para que un programa se comporte de distintas formas en función de si un predicado se encuentra o no presente ( inserta_modo).
En una consulta simple, las variables nos pueden servir para que Prolog encuentre un dato. tiene (X, coche).
Esta sentencia nos devuelve en la variable X el nombre de la persona que tiene un coche ( o de las personas que lo tienen, si hay mas de una en los hechos declarados).
La elección de un nombre adecuado para las variables suele hacer el programa más legible. Tiene (Persona, coche).
Como se puede observar, en Prolog no existe una instrucción de asignación, esto resulta una de las diferencias fundamentales entre Prolog y el resto de lenguajes de programación.
Las variables en Prolog toman valores al ser “igualadas” a constantes en los hechos o reglas del programa.
Hasta el momento en que una variable toma un valor, se dice que esta desintanciada; cuando ha tomado un valor, se dice que está instanciada a dicho valor.
Sin embargo, una variable sólo permanece instanciada hasta el momento en que obtenemos una solución, después, se desintancia y se procede a buscar nuevas soluciones.
Como consecuencia, no es posible utilizar las variables de Prolog para almacenar información.
predicates nondeterm gusto(symbol, symbol) clauses gusto (elena, leer). gusto (juan, computacion). gusto (juan , badminton). gusto (leonardo, badminton). gusto (erick, nadar). gusto (erick, leer). goal gusto (Persona, leer), gusto (Persona, nadar).
En la primera parte (objetivo) de la consulta, la variable Persona está desinstanciada, es decir, su valor es desconocido antes de que Prolog, intente encontrar una solución.
El segundo argumento, leer , es conocido.
De esta forma, Prolog, busca algún hecho que se pueda igualar o ajustar ( haga “matching” ) con la primera parte de la consulta, y lo hace en el orden en que éstos aparecen escritos en el programa.
El primer hecho del programa: gusto (elena, leer), se puede igualar a gusto (Persona, leer), ya que leer en la consulta es igual a leer en el hecho, por lo que se asignara el valor de elena a la variable Persona.
Al mismo tiempo, Prolog, se “anota” hasta donde ha llegado en la búsqueda de hechos que se pudieran igualar al primer objetivo de la consulta.
Ahora es necesario resolver el segundo objetivo de la consulta ( ya que la coma se interpresa como un “y”), buscando algo que se ajuste a gusto ( elena, nadar).
Prolog comienza la búsqueda desde el principio del programa, pero no consigue igualarlo con nada. Por tanto, el segundo objetivo de la consulta no es cierto cuando Persona toma el valor de elena
Entonces Prolog desinstancia la variable Persona, e intenta buscar una solución alternativa para el primer objetivo, para ello comienza a buscar otro hecho que se pueda igualar a gusto (Persona, leer).
A partir del punto en que se había anotado al encontrar la solución anterior. Este mecanismo es llamado de “vuelta atrás” (backtracking).
El nuevo hecho que se ajusta a la forma del primer objetivo es ahora gusto (erick, leer), entonces la variable Persona , se instancia a erick, y Prolog se dispone a buscar un hecho que se ajuste con el segundo objetivo de la consulta que ahora es gusto (erick, nadar).
En este caso Prolog tiene éxito y nos devuelve la solución: Persona = erick 1 Solution
El nuevo hecho que se ajusta a la forma del primer objetivo es ahora gusto (erick, leer), entonces la variable Persona , se instancia a erick, y Prolog se dispone a buscar un hecho que se ajuste con el segundo objetivo de la consulta que ahora es gusto (erick, nadar).
En este caso Prolog tiene éxito y nos devuelve la solución: Persona = erick 1 Solution
Cuando sólo deseamos extraer cierta información de una consulta, se usa un tipo especial de variables denominadas variables anónimas .
En Prolog, una variable anónima se denota por el símbolo de subrayado ( “_” ). pariente_de (Pariente, _).
La variable anónima se interpreta como “cualquier cosa” . Es decir, estamos preguntando:
¿Quién es padre o madre de alguien?, por lo que Prolog responde: Pariente = carlos Pariente = elena Pariente = tomas 3 Solutions
Las variables anónimas también se pueden usar en los hechos. tiene ( _ , zapatos). respira ( _ ).
Estarían expresando las sentencias del lenguaje natural. Todo el mundo tiene zapatos. Todo el mundo respira.
Finalmente, hay que tener en cuenta que una variable anónima se puede igualar a cualquier cosa, aunque su nombre no es relevante (no se produce ninguna instanciación).
Un objetivo del tipo : padre_de ( _ , _ ).
Se puede igualar perfectamente al hecho: padre_de (tomas, diana) ya que las dos apariciones de “_” en el objetivo se consideran variables distintas.
Aunque ambas palabras resultan equivalentes, en lo sucesivo nos referiremos a una consulta como un objetivo.
En el caso de que un objetivo conste de varias partes, como en: gusto (Persona, leer), gusto (Persona, nadar).
Se dirá que se trata de un objetivo compuesto, y a cada parte la llamaremos subobjetivo.
Los objetivos compuestos nos permiten obtener la “intersección” de varios subobjetivos.
Es posible usar un objetivo compuesto para encontrar una solución tal que el subobjetivo A y el subobjetivo B sean ciertos (una conjunción).
Además, también es posible buscar una solución tal que el subobjetivo A o el subobjetivo B sean ciertos.
Para ello separamos los subobjetivos por un punto y coma ( “ ; ” ).
Por ejemplo si nos interesara preguntar algo como: ¿Hay algún coche que cueste menos de 25,000?, escribiríamos un objetivo como: automovil ( Marca, Kilometraje, Caracteristicas, Costo), Costo < 25000.
Por el contrario, si deseamos averiguar algo como: ¿Hay algún coche que cueste menos de 25,000 o algun camión que cueste menos de 20,000?, el objetivo sería: automovil ( Marca, Kilometraje, Caracteristicas, Costo), Costo < 25,000; camion ( Marca, Kilometraje, Caracteristicas, Costo), Costo < 20,000.
Los comentarios en un programa Prolog se pueden escribir de dos formas distintas:
Si
se trata de un comentario que ocupa varias líneas, debemos comenzarlo con “ /* ” y terminarlo con “ */ ”.
Cuando
el comentario es breve podemos usar el símbolo de “ % “, el cuál provoca que todo lo que quede a su derecha en la misma línea se considere un comentario.
Se ocupa la frase “buscar el hecho que se ajuste con un objetivo” o “la variable se puede igualar a la constante”, etc. El objetivo pariente_de (tomas, diana) se ajusta al hecho pariente_de (tomas, diana).
Sin embargo, usualmente nos encontramos con variables involucradas. pariente_de(tomas, X) se ajusta al hecho pariente_de(tomas, diana).
Se ocupa la frase “buscar el hecho que se ajuste con un objetivo” o “la variable se puede igualar a la constante”, etc. El objetivo pariente_de (tomas, diana) se ajusta al hecho pariente_de (tomas, diana).
Sin embargo, usualmente nos encontramos con variables involucradas. pariente_de(tomas, X) se ajusta al hecho pariente_de(tomas, diana). La variable X tomara el valor de diana
Cuando una variable se encuentra instanciada, se comporta exactamente igual a una constante, es decir, si X se ha instanciado a diana, entonces: pariente_de(tomas,X) se ajusta a pariente_de(tomas,diana) pero pariente_de(tomas, X) no se ajusta a pariente_de(tomas, jose)
Por otra parte, dos variables desinstanciadas siempre se pueden ajustar. pariente_de(tomas, X) se ajusta a pariente_de(tomas, Y)
Instanciando las variables X e Y una a otra, mientras esta instanciación sea válida, las variables X e Y se tratarán como si fueran una única variable (dos punteros a una misma posición de memoria), de forma que si una toma un valor, la otra también lo tomará de forma inmediata.
La técnica con la que igualamos ( o ajustamos) un objetivo con la cabeza de una cláusula puede considerarse como un mecanismo de paso de parámetros (bidireccional) en Prolog.
Así que dada una llamada: pariente _de(tomas, Y) y una cláusula encabezada por: pariente_de (X, jose)
Tendríamos que pariente_de(tomas, Y) se ajusta a pariente_de(X,jose), instanciando la variable X a tomas y la variable Y a jose .
Esto se corresponde con el paso del parámetro tomas a la variable X y el paso del parámetro jose a la variable Y, en el momento de la llamada.
En Prolog, los valores a los que se instancian las variables se pueden pasar en dos modos: entrada (input), salida (output). Si una variable en la cabeza de una cláusula recibe un valor desde el objetivo, se dice que es un argumento de entrada y se denota por ( i ).
Si la variable sirve para devolver un valor al objetivo, se dice que es un argumento de salida y se denota por ( o ).
A la declaración de los distintos modos de un predicado lo llamamos patrón de flujo de datos.