Il y a de très bonnes réponses. Je vais essayer de contribuer à la discussion.
Au sujet de la programmation déclarative et logique dans Prolog, il y a le grand livre "The Craft of Prolog" de Richard O'Keefe . Il s'agit d'écrire des programmes efficaces en utilisant un langage de programmation qui vous permet d'écrire des programmes très inefficaces. Dans cet ouvrage, tout en discutant des implémentations efficaces de plusieurs algorithmes (dans le chapitre "Méthodes de programmation"), l'auteur adopte l'approche suivante:
- définir le problème en anglais
- écrire une solution de travail aussi déclarative que possible; généralement, cela signifie à peu près exactement ce que vous avez dans votre question, juste corriger Prolog
- à partir de là, prenez des mesures pour affiner la mise en œuvre pour la rendre plus rapide
L'observation la plus éclairante (pour moi) que j'ai pu faire en travaillant à travers ceux-ci:
Oui, la version finale de l'implémentation est beaucoup plus efficace que la spécification "déclarative" avec laquelle l'auteur a commencé. Il est toujours très déclaratif, succinct et facile à comprendre. Ce qui s'est passé entre les deux, c'est que la solution finale capture les propriétés du problème auquel la solution initiale était inconsciente.
En d'autres termes, lors de la mise en œuvre d'une solution, nous avons utilisé autant que possible nos connaissances sur le problème. Comparer:
Trouver une permutation d'une liste telle que tous les éléments sont dans l'ordre croissant
à:
La fusion de deux listes triées entraînera une liste triée. Puisqu'il peut y avoir des sous-listes qui sont déjà triées, utilisez-les comme point de départ, au lieu de sous-listes de longueur 1.
Petit bémol: une définition comme celle que vous avez donnée est séduisante car elle est très générale. Cependant, je ne peux pas échapper au sentiment qu'il ignore délibérément le fait que les permutations sont, bien, un problème combinatoire. C'est quelque chose que nous savons déjà ! Ce n'est pas une critique, juste une observation.
Quant à la vraie question: comment avancer? Eh bien, une façon consiste à fournir autant de connaissances sur le problème que nous déclarons à l'ordinateur.
La meilleure tentative que je connaisse pour vraiment résoudre le problème est présentée dans les livres co-écrits par Alexander Stepanov, "Elements of Programming" et "From Mathematics to Generic Programming" . Je ne suis malheureusement pas à la hauteur de tout résumer (ou même de bien comprendre) tout dans ces livres. Cependant, l'approche consiste à définir des algorithmes de bibliothèque et des structures de données efficaces (voire optimaux), sous réserve que toutes les propriétés pertinentes de l'entrée soient connues à l'avance. Le résultat final est:
- Chaque transformation bien définie est un raffinement des contraintes déjà en place (les propriétés connues);
- Nous laissons l'ordinateur décider de la transformation optimale en fonction des contraintes existantes.
Quant à savoir pourquoi cela ne s'est pas encore produit, eh bien, l'informatique est un domaine très jeune, et nous devons encore vraiment apprécier la nouveauté de la plupart d'entre eux.
PS
Pour vous donner un avant-goût de ce que je veux dire par "affiner l'implémentation": prenez par exemple le problème facile d'obtenir le dernier élément d'une liste, dans Prolog. La solution déclarative canonique est de dire:
last(List, Last) :-
append(_, [Last], List).
Ici, la signification déclarative de append/3
est:
List1AndList2
est la concaténation List1
etList2
Étant donné que dans le deuxième argument, append/3
nous avons une liste avec un seul élément et que le premier argument est ignoré (le trait de soulignement), nous obtenons un fractionnement de la liste d'origine qui supprime le début de la liste ( List1
dans le contexte de append/3
) et exige que le dos ( List2
dans le contexte de append/3
) est en effet une liste avec un seul élément: c'est donc le dernier élément.
La mise en œuvre réelle fournie par SWI-Prolog , cependant, dit:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
C'est encore bien déclaratif. Lisez de haut en bas:
Le dernier élément d'une liste n'a de sens que pour une liste d'au moins un élément. Le dernier élément pour une paire de queue et la tête d'une liste est donc: la tête, quand la queue est vide, ou la dernière de la queue non vide.
La raison pour laquelle cette implémentation est fournie est de contourner les problèmes pratiques entourant le modèle d'exécution de Prolog. Idéalement, cela ne devrait pas faire de différence quant à l'implémentation utilisée. De même, nous aurions pu dire:
last(List, Last) :-
reverse(List, [Last|_]).
Le dernier élément d'une liste est le premier élément de la liste inversée.
Si vous souhaitez obtenir un remplissage de discussions non concluantes sur ce qui est bon, déclaratif Prolog, passez simplement par certaines des questions et réponses dans la balise Prolog sur Stack Overflow .