Ci sono alcune ottime risposte. Proverò a contribuire alla discussione.
Sul tema della programmazione dichiarativa e logica in Prolog, c'è il grande libro "The Craft of Prolog" di Richard O ' Keefe. Si tratta di scrivere programmi efficienti usando un linguaggio di programmazione che ti permetta di scrivere programmi molto inefficienti. In questo libro, mentre si discute delle implementazioni efficienti di diversi algoritmi (nel capitolo "Metodi di programmazione"), l'autore adotta il seguente approccio:
- definisce il problema in inglese
- scrivere una soluzione di lavoro che sia il più dichiarativa possibile; di solito, questo significa praticamente esattamente quello che hai nella tua domanda, basta correggere Prolog
- da lì, prendi provvedimenti per perfezionare l'implementazione per renderla più veloce
L'osservazione più illuminante (per me) che sono riuscito a fare mentre ho lavorato su questi:
Sì, la versione finale dell'implementazione è molto più efficiente della specifica "dichiarativa" con cui l'autore ha iniziato. È ancora molto dichiarativo, succinto e facile da capire. Quello che è successo nel frattempo è che la soluzione finale cattura le proprietà del problema a cui la soluzione iniziale era ignara.
In altre parole, mentre implementavamo una soluzione, abbiamo usato la maggior parte delle nostre conoscenze sul problema che possiamo. Confronto:
Find a permutation of a list such that all elements are in ascending order
a:
Merging two sorted lists will result in a sorted list. Since there might be sublists that are already sorted, use these as a starting point, instead of sublists of length 1.
Un piccolo accenno: una definizione come quella che hai dato è attraente perché è molto generale. Tuttavia, non posso sfuggire alla sensazione che ignori intenzionalmente il fatto che le permutazioni sono, beh, un problema combinatorio. Questo è qualcosa che già conosciamo ! Questa non è una critica, solo un'osservazione.
Per quanto riguarda la vera domanda: come andare avanti? Bene, un modo è quello di fornire tanta conoscenza sul problema che stiamo dichiarando al computer.
Il miglior tentativo che io conosca per risolvere veramente il problema è presentato nei libri scritti da Alexander Stepanov, "Elementi di programmazione" e "Dalla matematica alla programmazione generica" . Purtroppo non sono in grado di riassumere (o anche di comprendere appieno) tutto in questi libri. Tuttavia, l'approccio consiste nel definire algoritmi di librerie e strutture dati efficienti (o persino ottimali), a condizione che tutte le proprietà rilevanti dell'input siano conosciute in anticipo. Il risultato finale è:
- Ogni trasformazione ben definita è un perfezionamento dei vincoli già esistenti (le proprietà note);
- Consentiamo al computer di decidere quale trasformazione è ottimale in base ai vincoli esistenti.
Per quanto riguarda il motivo per cui non è ancora successo, beh, l'informatica è un campo molto giovane, e stiamo ancora affrontando apprezzando veramente la novità di gran parte di esso.
PS
Per darti un'idea di cosa intendo per "perfezionare l'implementazione": prendi ad esempio il semplice problema di ottenere l'ultimo elemento di una lista, in Prolog. La soluzione dichiarativa canonica è:
last(List, Last) :-
append(_, [Last], List).
Qui, il significato dichiarativo di append/3
è:
List1AndList2
is the concatenation of List1
and List2
Poiché nel secondo argomento a append/3
abbiamo una lista con un solo elemento, e il primo argomento è ignorato (il carattere di sottolineatura), otteniamo una divisione dell'elenco originale che scarta la parte anteriore dell'elenco ( List1
nel contesto di append/3
) e richiede che la parte posteriore ( List2
nel contesto di append/3
) sia effettivamente una lista con un solo elemento: quindi, è l'ultimo elemento.
La implementazione effettiva fornito da SWI-Prolog , tuttavia, afferma:
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
Questo è ancora ben dichiarativo. Leggi dall'alto verso il basso, dice:
The last element of a list only makes sense for a list of at least one element.
The last element for a pair of the tail and the head of a list, then, is:
the head, when the tail is empty, or the last of the non-empty tail.
Il motivo per cui viene fornita questa implementazione è di aggirare i problemi pratici relativi al modello di esecuzione di Prolog. Idealmente, non dovrebbe fare la differenza quale applicazione viene utilizzata. Allo stesso modo, avremmo potuto dire:
last(List, Last) :-
reverse(List, [Last|_]).
The last element of a list is the first element of the reversed list.
Se vuoi fare il pieno di discussioni inconcludenti su ciò che è buono, Prolog dichiarativo, basta leggere alcune domande e risposte nel Tag Prolog su Stack Overflow .