Estamos acostumbrados a ver como paradigmas de programación compiten entre ellos por nuestra atención y van ganando o perdiendo fuerza por modas.
Hoy en día el paradigma más extendido (y por ello el que más veces es mal aplicado) es la programación orientada a objetos en alguna de sus variantes (clases como c# o java, prototipos como javascript o lua), pero también ha vuelto a estar de plena actualidad la programación funcional, bien sea con el auge de lenguajes eminentemente funcionales (F#, clojure, haskell) o con el uso de características más funcionales en otros lenguajes (linq en c#, scala, javascript, etc).
Además de estos dos paradigmas de programación existen otros menos conocidos pero que también tienen sus áreas de aplicación. Uno de ellos es la programación lógica.
Programación Lógica
OJO: No soy un experto (ni mucho menos) en programación lógica ni en teoría de lenguajes de programación en general, así que es muy probable que en este post haya inexactitudes y seguramente los términos que utilice no sean los más ortodoxos. Mi objetivo, aparte de aclarar mis ideas, es darte a conocer este tipo de programación y, si te interesa, siempre puedes ampliar la información, empezando por el artículo de la wikipedia sobre programación lógica.
La programación lógica es un paradigma de programación basado en la lógica de primer orden. Sí, esa lógica que te enseñan en el instituto (al menos en mis tiempos) y en la carrera donde tienes predicados y, mediante reglas, puedes derivar conclusiones:
Todos los hombres son mortales Socrates es un hombre ------------------------------ Socrates es mortal
Se trata de un tipo de programación declarativa, es decir, no se indica cómo se hacen las cosas, sino qué cosas hacer. A partir de eso, el motor de ejecución decide cómo hacerlo.
Esto, que antes podía sonar un poco raro, hoy en día es bastante habitual y lo encontramos por ejemplo en los databinding de XAML o Knockout, donde nos limitamos a definir qué datos se muestran en qué controles, y el framework se encarga de gestionar el resto.
Un ejemplo sencillo
Todo esto está muy bien, pero ¿qué pinta tendría un programa implementado con programación lógica?
En un programa lógico generalmente se definen un hechos y reglas, lo que se suele llamar base de conocimiento, y a partir de ellos, se pueden «obtener respuestas». Vamos a verlo con un ejemplo sencillo en pseudo-prolog en el que definimos la nacionalidad de varias personas y las relaciones de pertenencia entre paises y continentes.
% Hechos: es_español("Manolo"). es_italiano("Marco"). es_colombiano("Marcelo"). % Reglas: es_europeo(A) :- es_español(A). es_europeo(A) :- es_italiano(A). es_americano(A) :- es_colombiano(A). es_terricola(A) :- es_europeo(A). es_terricola(A) :- es_americano(A). son_del_mismo_continente(A,B) :- es_europeo(A), es_europeo(B). son_del_mismo_continente(A,B) :- es_americano(A), es_americano(B).
Las reglas indican que, si se cumple la parte derecha, entonces se cumple la parte izquierda. Por ejemplo, la primera regla se leería «si A es español, entonces A es europeo». La coma en la parte derecha funciona como un operador Y lógico entre las claúsulas que aparecen. La última regla sería «Si A es americano y B es americano, entonces son del mismo continente A y B».
A partir de la base de conocimiento que hemos definido, podemos establecer objetivos y el sistema intentará satisfacerlos e indicarnos si son ciertos o falsos:
?- son_del_mismo_continente("Manolo", "Marco"). yes
También podemos establecer objetivos abiertos y el sistema nos dirá qué valores hacen que se cumplan:
?- es_europeo(A). A = Manolo A = Marco
Además de este tipo de proposiciones basadas en hechos y relaciones, la mayoría de los lenguajes de programación lógica soportan también predicados basados en restricciones, del tipo X > 5 o A * Z = 58.
Cómo funciona todo esto
Generalmente se usa un mecanismo que se llama resolución SLD (algo así como resolución por selección lineal de claúsulas definidas) que se encarga de obtener todas las posibles formas de satisfacer el objetivo planteado. Explicar esto en profundidad lleva su tiempo, pero vamos a ver si nos podemos hacer una idea de cómo funciona:
Se parte del objetivo que queremos resolver y se van seleccionando reglas hasta intentar encontrar una solución. Esta selección de reglas se realiza de arriba a abajo, es decir, en el orden que están definidas, y una vez que se elige una regla, se resuelven recursivamente sus claúsulas de izquierda a derecha. Cuando hemos terminado, ya sea porque hemos encontrado una solución o porque hemos descubierto que no hay solución posible (no hay más reglas que aplicar), se realiza un backtracking y se toma la siguiente regla hasta procesar exhaustivamente todas las reglas definidas.
En realidad el proceso es un poco más complicado que eso, pero para hacernos una idea de cómo funciona, nos sirve.
Vamos a verlo con un ejemplo sobre el programa anterior:
son_del_mismo_continente("Manolo", A).
Estamos preguntando al sistema qué valores de A cumplen que son del mismo continente que «Manolo». Para resolverlo, empezaríamos por tomar la primera regla que tenemos para son_del_mismo_continente
y la aplicaríamos, conviertiendo nuestro objetivo en:
es_europeo("Manolo"), es_europeo(B).
Ahora procesaríamos cada objetivo de izquierda a derecha. Aplicando la regla es_europeo(A) :- es_español(A)
, nos quedaría:
es_español("Manolo"), es_europeo(B).
Que Manolo es español lo sabemos porque está definido directamente (es_español("Manolo")
), así que pasaríamos a resolver la segunda claúsula:
es_europeo(B).
Ahora habría que encontrar todos aquellos valores de B para los que se cumple que B es europeo, y así alcanzaríamos la solución. Como eso es un poco largo, os dejo un video en el que alguien se entretiene haciendo una resolución SLD en Prolog.
Lenguajes y librerías
El lenguaje de programación lógica por excelencia es Prolog, cuya implementación más extendida (al menos en Windows) es SWI-Prolog. Es un lenguaje puramente lógico, por lo que normalmente se suele usar para partes muy concretas de un sistema, mezclándolo con otros lenguajes.
Existen librerías que permiten aplicar estas técnicas de programación en otros lenguajes. En ese sentido, seguramente las más conocidas sean las basadas en miniKanren, un DSL que se ha portado a lenguajes como Clojure, Haskell, Javascript, Python, Ruby, etc., y permite utilizar este tipo de programación.
Conclusiones
Aunque a primera vista pueda parecer que este tipo de programación está muy limitada a resolver puzzles y cosas por el estilo, lo cierto es que (teóricamente) puedes realizar cualquier tipo de programa con ellos.
Aun así, lo lógico (valga la redundacia) es utilizar programación lógica en las áreas en que más sentido tiene: inteligencia artificial, sistemas expertos, procesamiento de lenguajes, etc.
El poder utilizar este paradigma a través de librerías hace sea mucho más atractivo, porque hace que sea más sencillo usarlo sólo en aquellas partes del problema que tiene sentido.
Si tienes curiosidad por profundizar en el tema, te recomiendo que le eches un vistazo a From Logic to Logic Programming, que aunque sea un libro bastante teórico ayuda a sentar las bases y, sobre todo, The Reasoned Schemer
, que va explicando poco a poco como construir un sistema de resolución similar al expuesto en el post usando Scheme.
¡Muy interesante tu artículo! Solo una cosa:
Ahora habría que encontrar todos aquellos valores de A para los que se cumple que A es europeo, y así alcanzaríamos la solución. Como eso es un poco largo, os dejo un video en el que alguien se entretiene haciendo una resolución SLD en Prolog.
Entiendo que quieres decir encontrar valores de B
Saludos
Tienes toda la razón, muchas gracias por el aviso.
VALES HARTA CON TU PUTO PROGRAMA
Hola, interesante el articulo felicidades.