Importante: los fragmentos de Python y de Haskell de este tema se ejecutan en el navegador con los intérpretes didácticos del curso. El código de Prolog también. Los fragmentos de SQL y de Datalog "puro" que aparecen como texto no se ejecutan (Datalog sí tiene su propio evaluador en el laboratorio de la sección 9).
Objetivo de aprendizaje
Al concluir este tema, el estudiante será capaz de:
- Definir relaciones sobre listas con recursión estructural (caso base y caso recursivo) y leer programas en Prolog sin ejecutarlos.
- Usar acumuladores y distinguir una versión de crecimiento cuadrático de otra lineal, midiéndolas con las inferencias.
- Combinar aritmética y recursión, y reconocer cuándo una relación pierde la reversibilidad.
- Representar árboles con términos y definir relaciones sobre ellos.
- Reunir todas las respuestas con
findally explicar en qué se diferencian Prolog y Datalog. - Comparar la misma solución en Python, Haskell y Prolog, y argumentar cuándo conviene el enfoque lógico.
Ruta de trabajo del tema
| Etapa | Contenido | Producto del estudiante |
|---|---|---|
| 1 | Planteamiento inicial | Hipótesis escrita |
| 2 | Recursión sobre listas | longitud, miembro y concatenar, con sus tres lenguajes |
| 3 | Acumuladores | Comparación de dos versiones de reversa |
| 4 | Aritmética y recursión | factorial y sus límites |
| 5 | Ordenar con quicksort | Solución en tres paradigmas |
| 6 | Árboles | Búsqueda y recorrido |
| 7 | Generar y probar | Permutaciones y reinas |
| 8 | Reunir respuestas | findall y forall |
| 9 | Datalog | Evaluación hasta el punto fijo |
| 10 | La misma solución en tres paradigmas | Síntesis para el proyecto |
| 11 | Verificación | Cuestionario y respuesta revisada |
1. Planteamiento inicial
Se desea definir la relación miembro(X, Lista): «X es un elemento de Lista». En Python se escribiría x in lista, que ya viene incluido. En Prolog, member/2 también viene en la biblioteca, pero el objetivo es entender cómo se define.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Esta hipótesis se retomará al final del tema.
2. Recursión sobre listas
Las listas de Prolog tienen exactamente dos formas, igual que en Haskell: la lista vacía [] o una cabeza seguida de una cola, [H|T]. Una definición recursiva tiene entonces una cláusula por cada forma.
Plantilla de tres preguntas (la misma de la Unidad IV, ahora con relaciones):
- ¿Qué ocurre con la lista vacía? (caso base)
- Si la lista tiene cabeza y cola, ¿cómo se relaciona la solución de la cola con la solución de la lista completa? (caso recursivo)
- ¿Qué argumento adicional hace falta para expresar el resultado? (argumento de salida)
Longitud
% longitud(Lista, N): N es la cantidad de elementos de Lista.
longitud([], 0).
longitud([_|Resto], N) :- longitud(Resto, M), N is M + 1.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
La misma idea en los tres paradigmas. Ejecute cada columna y observe las diferencias en la tabla.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Pertenencia y concatenación
% concatenar(Xs, Ys, Zs): Zs es Xs seguida de Ys.
concatenar([], Ys, Ys).
concatenar([X|Xs], Ys, [X|Zs]) :- concatenar(Xs, Ys, Zs).
% miembro(X, Lista): X es un elemento de Lista.
miembro(X, [X|_]).
miembro(X, [_|Resto]) :- miembro(X, Resto).
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
La pregunta inicial del tema se responde con dos casos: X es miembro si es la cabeza, o si es miembro de la cola. Y como las relaciones no distinguen entradas de salidas, la misma definición genera los elementos cuando X queda sin ligar.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Nota: las relaciones de la biblioteca (
member/2,append/3,length/2…) se definen de esta manera. El intérprete del curso incluye una pequeña biblioteca escrita en Prolog; si usted define un predicado con el mismo nombre, el suyo reemplaza al de la biblioteca.
3. Acumuladores
Una solución se puede escribir de dos maneras: dejando que el resultado se construya al regresar de la recursión (como longitud), o acumulándolo en un argumento extra mientras se desciende. La segunda técnica es la misma que, en la Unidad IV, convirtió un ciclo en una recursión con acumulador.
Invertir una lista ilustra la diferencia. La versión ingenua concatena en cada paso; la del acumulador coloca cada elemento al frente de una lista que se va construyendo.
% reversa(Lista, Inversa) usando un acumulador.
reversa(L, R) :- reversa(L, [], R).
reversa([], Acc, Acc).
reversa([X|Xs], Acc, R) :- reversa(Xs, [X|Acc], R).
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Medir el trabajo: las inferencias
El motor cuenta cada llamada a un predicado como una inferencia. Es una medida independiente de la velocidad del equipo, equivalente a contar pasos de reducción en Haskell.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
| Longitud de la lista | Versión ingenua (concatenar) | Con acumulador |
|---|---|---|
| 5 | 21 inferencias | 7 |
| 10 | 66 | 12 |
| 20 | 231 | 22 |
| 40 | 861 | 42 |
El trabajo de la versión ingenua crece con el cuadrado de la longitud (cada paso vuelve a recorrer lo ya invertido); el de la versión con acumulador crece linealmente. Duplicar la lista cuadruplica el trabajo de la primera y solo duplica el de la segunda.
Error común — optimizar sin medir: antes de reescribir un predicado, mida las inferencias de las dos versiones con las mismas consultas. A veces la versión "más elegante" es también la más lenta.
4. Aritmética y recursión
El factorial mezcla recursión y aritmética. Dado que is exige valores ya conocidos, el orden de las metas importa.
% factorial(N, F): F es N!.
factorial(0, 1).
factorial(N, F) :- N > 0, M is N - 1, factorial(M, G), F is N * G.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Importante:
factorial(5, F)daF = 120;factorial(N, 120)produce un error de «instanciación» porqueN > 0yM is N - 1necesitan que N tenga ya un valor. Las relaciones aritméticas de Prolog estándar no son reversibles. Hay extensiones (restricciones, CLP) que lo resuelven, pero están fuera del alcance del curso.
5. Ordenar con quicksort
El algoritmo es el mismo que se programó en Haskell en el tema 4.3: elegir un pivote, repartir el resto en menores y mayores, ordenar cada parte y unir. En Prolog cada reparto es una relación.
% quicksort(Lista, Ordenada)
quicksort([], []).
quicksort([P|Resto], S) :-
particion(Resto, P, Menores, Mayores),
quicksort(Menores, SM),
quicksort(Mayores, SMa),
append(SM, [P|SMa], S).
% particion(Lista, Pivote, Menores, Mayores)
particion([], _, [], []).
particion([X|Xs], P, [X|Ms], Ns) :- X =< P, particion(Xs, P, Ms, Ns).
particion([X|Xs], P, Ms, [X|Ns]) :- X > P, particion(Xs, P, Ms, Ns).
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Nota: el parámetro
X =< Pse escribe con=<, no<=. ConX < PyX >= Pel resultado sería el mismo, salvo para elementos iguales al pivote (que caerían en el otro lado).
6. Árboles
Prolog no necesita declarar tipos de datos: cualquier estructura se puede usar como nodo. Un árbol binario de búsqueda se representa con dos formas de término: nil (el árbol vacío) y arbol(Izquierdo, Valor, Derecho).
| Haskell (tema 4.3) | Prolog |
|---|---|
data Arbol = Hoja | Nodo Arbol Int Arbol |
(no se declara; se usan los términos nil y arbol(I, V, D)) |
Nodo Hoja 5 Hoja |
arbol(nil, 5, nil) |
| Un constructor por forma del tipo; el compilador verifica los casos | Los patrones de las cláusulas hacen el trabajo; nadie verifica que se cubran todos los casos |
% Un árbol es nil o arbol(Izquierdo, Valor, Derecho).
% pertenece(X, Arbol): X está en el árbol.
pertenece(X, arbol(_, X, _)).
pertenece(X, arbol(I, V, _)) :- X < V, pertenece(X, I).
pertenece(X, arbol(_, V, D)) :- X > V, pertenece(X, D).
% inorden(Arbol, Lista): Lista contiene los valores en orden.
inorden(nil, []).
inorden(arbol(I, V, D), L) :-
inorden(I, LI), inorden(D, LD), append(LI, [V|LD], L).
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
7. Generar y probar
En la Unidad IV se vio que el paradigma funcional resuelve muchos problemas transformando datos. El paradigma lógico resuelve muchos otros buscando: se describe qué es una solución y el motor explora las posibilidades. La técnica más simple es generar y probar: producir candidatos y descartar los que no cumplen.
% perm(Lista, Permutacion)
perm([], []).
perm(L, [H|T]) :- select(H, L, R), perm(R, T).
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
El problema de las reinas
Colocar N reinas en un tablero de N × N sin que dos se ataquen. Se representa una solución como una lista de N números: el i-ésimo es la fila de la reina de la columna i. La versión A genera todas las permutaciones y después comprueba cuáles son seguras; la B coloca una reina a la vez y comprueba al colocarla, descartando ramas inútiles tan pronto como es posible.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
La diferencia (unas 18 000 inferencias contra unas 3 700 con seis reinas) crece muy rápido con N. El principio es el mismo que en el mapa del tema 5.2: comprobar lo antes posible.
Definición — Generar y probar: estrategia que produce candidatos y luego comprueba una condición. Es simple y correcta, pero costosa. Su mejora consiste en comprobar condiciones parciales durante la generación, como hace la versión B.
8. Reunir todas las respuestas
Con el retroceso, las respuestas aparecen una por una y se pierden. A veces se necesita una lista con todas, por ejemplo para contarlas o para operar con ellas. findall/3 hace exactamente eso:
Definición —
findall(Plantilla, Meta, Lista): explora todas las soluciones deMetay reúne enListauna copia dePlantillapor cada una. Si no hay soluciones,Listaes[].
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
| Quiero… | En Python | En Haskell | En Prolog |
|---|---|---|---|
| Los elementos que cumplen una condición | [x for x in xs if cond(x)] |
[x | x <- xs, cond x] |
findall(X, (member(X, Xs), cond(X)), L) |
| Cuántos son | len(...) |
length ... |
length(L, N) después del findall |
| Si todos cumplen una condición | all(...) |
all cond xs |
forall(member(X, Xs), cond(X)) |
Nota:
findallyforallestán fuera de la lógica pura: examinan el árbol de búsqueda completo de una meta. Son útiles y de uso corriente, pero rompen la lectura declarativa simple del programa.
9. Datalog
Prolog es un lenguaje de propósito general. Datalog es un subconjunto pensado para consultar bases de datos: conserva los hechos y las reglas, pero elimina todo lo que hace impredecible la ejecución.
| Característica | Prolog | Datalog |
|---|---|---|
| Términos | Cualquier estructura y listas | Solo constantes y variables |
| Aritmética, corte, negación | Sí | No en el núcleo |
| Estrategia | De arriba hacia abajo: de la consulta hacia los hechos, en profundidad con retroceso | De abajo hacia arriba: de los hechos hacia las consecuencias, hasta no deducir nada nuevo |
| El orden de las cláusulas | Importa | No importa |
| Terminación | Puede no terminar | Siempre termina (el número de hechos posibles es finito) |
| Resultado | Una respuesta a la vez | El conjunto completo de respuestas |
| Uso típico | Programas de propósito general, inteligencia artificial simbólica | Consultas recursivas sobre bases de datos, análisis estático de programas |
Definición — Punto fijo: estado en el que aplicar todas las reglas a todos los hechos conocidos ya no produce ningún hecho nuevo. Datalog calcula las consecuencias de un programa repitiendo las reglas hasta alcanzarlo.
Cada regla de Datalog debe ser segura: toda variable de la cabeza aparece en alguna meta del cuerpo. Eso garantiza que el conjunto de hechos deducibles sea finito.
Prolog contra Datalog en un grafo con ciclo
El grafo del laboratorio tiene el ciclo a → b → c → a. Observe la evaluación ascendente: en cada ronda se deducen los hechos nuevos, y en la última ya no aparece ninguno. Después compare con lo que hace la búsqueda de Prolog sobre el mismo programa.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Prolog, que busca de arriba hacia abajo, entra en el ciclo y repite las respuestas indefinidamente; Datalog, que calcula conjuntos, termina siempre y devuelve cada respuesta una sola vez.
Datalog y SQL
La consulta recursiva del grafo se escribe en SQL con una expresión de tabla recursiva. El fragmento es ilustrativo y no se ejecuta en esta página; verifique su sintaxis con el gestor de bases de datos que utilice.
WITH RECURSIVE camino(origen, destino) AS (
SELECT origen, destino FROM arco
UNION
SELECT a.origen, c.destino
FROM arco a JOIN camino c ON a.destino = c.origen
)
SELECT destino FROM camino WHERE origen = 'a';
| Datalog | SQL |
|---|---|
camino(X, Y) :- arco(X, Y). |
SELECT origen, destino FROM arco |
camino(X, Y) :- arco(X, Z), camino(Z, Y). |
SELECT a.origen, c.destino FROM arco a JOIN camino c ON a.destino = c.origen |
La consulta ?- camino(a, X). |
SELECT destino FROM camino WHERE origen = 'a' |
Nota: la
UNION(en lugar deUNION ALL) elimina los duplicados y es lo que permite que la consulta termine en un grafo con ciclos, igual que el punto fijo de Datalog.
10. La misma solución en tres paradigmas
El curso cierra comparando. Ya se vio longitud, concatenar, quicksort y factorial en los tres lenguajes. Falta uno que sintetiza las diferencias: encontrar el mayor elemento de una lista.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
| Orientado a objetos / imperativo (Python) | Funcional (Haskell) | Lógico (Prolog) | |
|---|---|---|---|
| Qué se escribe | Instrucciones y objetos con estado | Funciones y expresiones | Hechos, reglas y consultas |
| Qué se ejecuta | Una secuencia de pasos | Una reducción de expresiones | Una búsqueda de demostración |
| Dónde está el resultado | En el valor devuelto o en el estado de un objeto | En el valor de la expresión | En las ligaduras de las variables de la consulta |
| Quién controla el orden | La persona que programa | La estrategia de evaluación | El motor de búsqueda, con un orden fijo |
| Se puede preguntar al revés | No | No | Sí, en las relaciones puras |
| Lo que da seguridad | Encapsulamiento y pruebas | Pureza y tipos | Corrección declarativa de las reglas |
| Riesgo típico | Estado compartido inesperado | Evaluación perezosa que consume memoria | Recursión por la izquierda y cortes rojos |
| Problemas naturales | Simulaciones, interfaces, sistemas con entidades | Transformaciones de datos, compiladores | Búsqueda, restricciones, reglas de conocimiento, consultas |
Definición — Estudio comparativo: análisis de una misma solución en varios paradigmas para contrastar sus ventajas y desventajas. Es la evidencia final que pide el programa de la unidad de aprendizaje: el proyecto de esta unidad (Unidad V - Proyecto - Un mismo problema en tres paradigmas) la construye con un problema propio.
11. Errores frecuentes y verificación
Error común — olvidar el caso base: una definición recursiva sin caso base produce una búsqueda infinita. Escriba primero la cláusula de la lista vacía.
Error común — escribir
X = Y + 1esperando un cálculo:=unifica estructuras; para calcular se usais.
Error común — olvidar que
isexige valores ya conocidos: si la variable del lado derecho aún no tiene valor, hay un error de instanciación. Revise el orden de las metas.
Error común — usar
findallcuando basta la propia relación: reunir respuestas en una lista pierde la capacidad de preguntar "al revés". Úselo solo cuando de verdad necesite la lista.
Error común — olvidar condiciones excluyentes: si dos cláusulas se aplican al mismo caso (como
X =< PyX >= Pcon X igual a P), aparecen respuestas duplicadas o incorrectas. Escriba condiciones que se excluyan.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
12. Libreta de trabajo
Este laboratorio interactivo requiere JavaScript habilitado en el navegador.
13. Preguntas de reflexión
- Defina en Prolog la relación
ultimo(Lista, X)(«X es el último elemento»). Pruebe que funciona al revés: ¿qué respondeultimo(L, 3)? ¿Por qué la consulta no termina después de la primera respuesta? - Escriba
sumatoria(Lista, S)de dos maneras: sin acumulador y con acumulador. Mida las inferencias de ambas con una lista de 20 elementos. ¿Cuál esperaba que fuera más eficiente y qué encontró? - Para el problema de las reinas, explique con sus palabras por qué la versión B hace menos trabajo aun cuando describe el mismo problema. ¿Qué otra restricción podría comprobarse antes?
- Elija un problema que conozca de otra materia (horarios, rutas, reglas de una empresa) y explique si conviene resolverlo con Prolog, con Datalog o con otro paradigma. Justifique con las características de cada uno.
- Compare la reversibilidad de
concatenarcon el hecho de quefactorial(N, 120)produzca un error. ¿Qué tienen en común y en qué difieren las dos situaciones?
14. Lecturas recomendadas
- Sterling, L., & Shapiro, E. (1994). The art of Prolog (2nd ed.). MIT Press. (Capítulos sobre listas, árboles y programación con acumuladores; disponible en la bóveda: 08-Referencias.)
- Clocksin, W. F., & Mellish, C. S. (2003). Programming in Prolog (5th ed.). Springer. (Capítulos sobre listas, aritmética y estructuras de datos; disponible en la bóveda.)
- Bratko, I. (1986). Prolog programming for artificial intelligence. Addison-Wesley. (Capítulos sobre listas y árboles, y problemas de búsqueda; disponible en la bóveda.)
- Gabbrielli, M., & Martini, S. (2010). Programming languages: principles and paradigms. Springer. (Capítulo sobre el paradigma lógico, incluida la sección sobre Datalog.)
- Ceri, S., Gottlob, G., & Tanca, L. (1989). What you always wanted to know about Datalog (and never dared to ask). IEEE Transactions on Knowledge and Data Engineering, 1(1), 146–166.