Hay algunas muy buenas respuestas. Intentaré contribuir a la discusión.
Sobre el tema de la programación declarativa y lógica en Prolog, está el gran libro "The Craft of Prolog" de Richard O'Keefe . Se trata de escribir programas eficientes utilizando un lenguaje de programación que le permite escribir programas muy ineficientes. En este libro, mientras discute las implementaciones eficientes de varios algoritmos (en el capítulo "Métodos de programación"), el autor adopta el siguiente enfoque:
- define el problema en inglés
- escriba una solución de trabajo que sea lo más declarativa posible; por lo general, eso significa exactamente lo que tienes en tu pregunta, solo corrige Prolog
- a partir de ahí, tome medidas para refinar la implementación para que sea más rápida
La observación más esclarecedora (para mí) que pude hacer mientras trabajaba a través de estos:
Sí, la versión final de la implementación es mucho más eficiente que la especificación "declarativa" con la que comenzó el autor. Todavía es muy declarativo, sucinto y fácil de entender. Lo que sucedió en el medio es que la solución final captura las propiedades del problema al que la solución inicial era ajena.
En otras palabras, al implementar una solución, hemos utilizado tanto de nuestro conocimiento sobre el problema como hemos podido. Comparar:
Encuentre una permutación de una lista de manera que todos los elementos estén en orden ascendente
a:
Fusionar dos listas ordenadas dará como resultado una lista ordenada. Como puede haber sublistas que ya están ordenadas, úselas como punto de partida, en lugar de sublistas de longitud 1.
Un pequeño aparte: una definición como la que has dado es atractiva porque es muy general. Sin embargo, no puedo escapar de la sensación de que ignora deliberadamente el hecho de que las permutaciones son, bueno, un problema combinatorio. ¡Esto es algo que ya sabemos ! Esto no es una crítica, solo una observación.
En cuanto a la verdadera pregunta: ¿cómo avanzar? Bueno, una forma es proporcionar tanto conocimiento sobre el problema que estamos declarando a la computadora.
El mejor intento que conozco para resolver realmente el problema se presenta en los libros escritos por Alexander Stepanov, "Elementos de programación" y "De las matemáticas a la programación genérica" . Lamentablemente, no estoy a la altura de la tarea de resumir (o incluso comprender completamente) todo en estos libros. Sin embargo, el enfoque es definir algoritmos de biblioteca y estructuras de datos eficientes (o incluso óptimos), con la condición de que todas las propiedades relevantes de la entrada se conozcan de antemano. El resultado final es:
- Cada transformación bien definida es un refinamiento de las restricciones que ya existen (las propiedades que se conocen);
- Dejamos que la computadora decida qué transformación es óptima en función de las restricciones existentes.
En cuanto a por qué todavía no ha sucedido, bueno, la informática es un campo muy joven, y todavía estamos haciendo frente a apreciar realmente la novedad de la mayoría.
PD
Para darle una idea de lo que quiero decir con "refinar la implementación": tomemos, por ejemplo, el problema fácil de obtener el último elemento de una lista, en Prolog. La solución declarativa canónica es decir:
last(List, Last) :-
append(_, [Last], List).
Aquí, el significado declarativo de append/3
es:
List1AndList2
es la concatenación de List1
yList2
Dado que en el segundo argumento append/3
tenemos una lista con solo un elemento, y el primer argumento es ignorado (el guión bajo), obtenemos una división de la lista original que descarta el frente de la lista ( List1
en el contexto de append/3
) y exige que la parte posterior ( List2
en el contexto de append/3
) es de hecho una lista con un solo elemento: por lo tanto, es el último elemento.
La implementación real proporcionada por SWI-Prolog , sin embargo, dice:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
Esto sigue siendo muy bien declarativo. Lea de arriba a abajo, dice:
El último elemento de una lista solo tiene sentido para una lista de al menos un elemento. El último elemento para un par de la cola y el encabezado de una lista, entonces, es: el encabezado, cuando la cola está vacía, o el último de la cola no vacía.
La razón por la que se proporciona esta implementación es para solucionar los problemas prácticos que rodean el modelo de ejecución de Prolog. Idealmente, no debería marcar la diferencia qué implementación se utiliza. Del mismo modo, podríamos haber dicho:
last(List, Last) :-
reverse(List, [Last|_]).
El último elemento de una lista es el primer elemento de la lista invertida.
Si desea llenar sus discusiones inconclusas sobre lo que es bueno, el Prolog declarativo, simplemente revise algunas de las preguntas y respuestas en la etiqueta Prolog en Stack Overflow .