Es gibt einige sehr gute Antworten. Ich werde versuchen, zur Diskussion beizutragen.
Zum Thema deklarative, logische Programmierung in Prolog gibt es das großartige Buch "The Craft of Prolog" von Richard O'Keefe . Es geht darum, effiziente Programme mit einer Programmiersprache zu schreiben, mit der Sie sehr ineffiziente Programme schreiben können. In diesem Buch geht der Autor, während er die effiziente Implementierung mehrerer Algorithmen (im Kapitel "Methoden der Programmierung") diskutiert, folgendermaßen vor:
- Definieren Sie das Problem auf Englisch
- Schreiben Sie eine funktionierende Lösung, die so aussagekräftig wie möglich ist. Normalerweise bedeutet das ziemlich genau das, was Sie in Ihrer Frage haben, nur korrekter Prolog
- Ergreifen Sie von dort aus Schritte, um die Implementierung zu verfeinern und zu beschleunigen
Die (für mich) aufschlussreichste Beobachtung, die ich machen konnte, als ich mich durch diese Punkte arbeitete:
Ja, die endgültige Version der Implementierung ist viel effizienter als die "deklarative" Spezifikation, mit der der Autor begonnen hat. Es ist immer noch sehr aussagekräftig, prägnant und leicht zu verstehen. Was dazwischen passiert ist, ist, dass die endgültige Lösung Eigenschaften des Problems erfasst, auf das die ursprüngliche Lösung nicht eingegangen ist.
Mit anderen Worten, wir haben bei der Implementierung einer Lösung so viel Wissen wie möglich über das Problem verwendet. Vergleichen Sie:
Suchen Sie eine Permutation einer Liste, sodass alle Elemente in aufsteigender Reihenfolge angezeigt werden
zu:
Das Zusammenführen von zwei sortierten Listen führt zu einer sortierten Liste. Da es möglicherweise bereits sortierte Unterlisten gibt, sollten Sie diese anstelle von Unterlisten der Länge 1 als Ausgangspunkt verwenden.
Eine kleine Ausnahme: Eine Definition wie die, die Sie gegeben haben, ist attraktiv, weil sie sehr allgemein ist. Ich kann mich jedoch dem Gefühl nicht entziehen, die Tatsache, dass Permutationen ein kombinatorisches Problem sind, bewusst zu ignorieren. Das wissen wir schon ! Dies ist keine Kritik, nur eine Beobachtung.
Zur eigentlichen Frage: Wie geht es weiter? Eine Möglichkeit besteht darin, dem Computer so viel Wissen über das Problem zu vermitteln, das wir melden.
Der beste Versuch, den ich kenne, um das Problem wirklich zu lösen, wird in den von Alexander Stepanov mitverfassten Büchern "Elements of Programming" und "From Mathematics to Generic Programming" vorgestellt . Ich bin leider nicht in der Lage, alles in diesen Büchern zusammenzufassen (oder sogar vollständig zu verstehen). Der Ansatz besteht jedoch darin, effiziente (oder sogar optimale) Bibliotheksalgorithmen und Datenstrukturen zu definieren, sofern alle relevanten Eigenschaften der Eingabe im Voraus bekannt sind. Das Endergebnis ist:
- Jede wohldefinierte Transformation ist eine Verfeinerung der bereits vorhandenen Einschränkungen (der bekannten Eigenschaften).
- Wir lassen den Computer entscheiden, welche Transformation auf der Grundlage der vorhandenen Einschränkungen optimal ist.
Was den Grund angeht, warum es noch nicht so weit ist, ist die Informatik ein sehr junges Gebiet, und wir werden immer noch damit fertig, die Neuheit des größten Teils davon wirklich zu würdigen.
PS
Um Ihnen einen Eindruck davon zu geben, was ich unter "Verfeinern der Implementierung" verstehe: Nehmen Sie zum Beispiel das einfache Problem, das letzte Element einer Liste in Prolog zu erhalten. Die kanonisch deklarative Lösung lautet:
last(List, Last) :-
append(_, [Last], List).
Hier ist die deklarative Bedeutung von append/3
:
List1AndList2
ist die Verkettung von List1
undList2
Da append/3
wir im zweiten Argument eine Liste mit nur einem Element haben und das erste Argument ignoriert wird (der Unterstrich), erhalten wir eine Teilung der ursprünglichen Liste, die den Anfang der Liste ( List1
im Kontext von append/3
) verwirft und dies verlangt Das Back ( List2
im Kontext von append/3
) ist in der Tat eine Liste mit nur einem Element: Es ist also das letzte Element.
In der tatsächlichen Implementierung von SWI-Prolog heißt es jedoch:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
Das ist immer noch schön aussagekräftig. Lesen Sie von oben nach unten, heißt es:
Das letzte Element einer Liste ist nur für eine Liste mit mindestens einem Element sinnvoll. Das letzte Element für ein Paar aus dem Schwanz und dem Kopf einer Liste ist dann: der Kopf, wenn der Schwanz leer ist, oder der letzte des nicht leeren Schwanzes.
Der Grund, warum diese Implementierung bereitgestellt wird, besteht darin, die praktischen Probleme im Zusammenhang mit dem Ausführungsmodell von Prolog zu umgehen. Im Idealfall sollte es keinen Unterschied machen, welche Implementierung verwendet wird. Ebenso hätten wir sagen können:
last(List, Last) :-
reverse(List, [Last|_]).
Das letzte Element einer Liste ist das erste Element der umgekehrten Liste.
Wenn Sie eine Fülle von nicht schlüssigen Diskussionen über das Gute, das deklarative Prolog führen möchten, gehen Sie einfach einige der Fragen und Antworten im Prolog-Tag zu Stack Overflow durch .