Ci sono alcune risposte molto buone. 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 utilizzando un linguaggio di programmazione che consente di scrivere programmi molto inefficienti. In questo libro, mentre discute le implementazioni efficienti di numerosi algoritmi (nel capitolo "Metodi di programmazione"), l'autore adotta il seguente approccio:
- definire il problema in inglese
- scrivere una soluzione funzionante il più dichiarativa possibile; di solito, questo significa praticamente esattamente quello che hai nella tua domanda, solo Prolog corretto
- da lì, prendere provvedimenti per perfezionare l'implementazione per renderla più veloce
L'osservazione più illuminante (per me) che sono stato in grado di fare mentre mi facevo strada attraverso questi:
Sì, la versione finale dell'implementazione è molto più efficiente della specifica "dichiarativa" con cui l'autore ha iniziato. È ancora molto dichiarativo, conciso e di facile comprensione. Ciò che è accaduto nel mezzo è che la soluzione finale cattura le proprietà del problema a cui la soluzione iniziale era ignara.
In altre parole, durante l'implementazione di una soluzione, abbiamo utilizzato quante più conoscenze possibili sul problema. Confrontare:
Trova una permutazione di un elenco in modo tale che tutti gli elementi siano in ordine crescente
a:
L'unione di due elenchi ordinati comporterà un elenco ordinato. Poiché potrebbero essere presenti elenchi già ordinati, utilizzarli come punto di partenza, anziché elenchi di lunghezza 1.
Un piccolo inconveniente: 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à sappiamo ! Questa non è una critica, solo un'osservazione.
Per quanto riguarda la vera domanda: come andare avanti? Bene, un modo è quello di fornire la stessa conoscenza del problema che stiamo dichiarando al computer.
Il miglior tentativo che conosco per risolvere davvero il problema è presentato nei libri scritti da Alexander Stepanov, "Elementi di programmazione" e "Dalla matematica alla programmazione generica" . Purtroppo non sono all'altezza del compito di riassumere (o persino comprendere appieno) tutto in questi libri. Tuttavia, l'approccio è quello di definire algoritmi e strutture di dati efficienti (o addirittura ottimali), a condizione che tutte le proprietà pertinenti dell'input siano note in anticipo. Il risultato finale è:
- Ogni trasformazione ben definita è un affinamento dei vincoli che sono già in atto (le proprietà conosciute);
- Lasciamo che il computer decida quale trasformazione è ottimale in base ai vincoli esistenti.
Per quanto riguarda il motivo per cui non è ancora del tutto accaduto, beh, l'informatica è un campo davvero giovane e stiamo ancora affrontando apprezzando davvero la novità di gran parte di esso.
PS
Per darti un'idea di cosa intendo "perfezionando l'implementazione": prendi ad esempio il semplice problema di ottenere l'ultimo elemento di un elenco, in Prolog. La soluzione dichiarativa canonica è quella di dire:
last(List, Last) :-
append(_, [Last], List).
Qui, il significato dichiarativo di append/3
è:
List1AndList2
è la concatenazione di List1
eList2
Dato che nel secondo argomento append/3
abbiamo una lista con un solo elemento, e il primo argomento viene ignorato (il trattino basso), otteniamo una divisione della lista originale che scarta il fronte della lista ( List1
nel contesto di append/3
) e richiede che il retro ( List2
nel contesto di append/3
) è davvero un elenco con un solo elemento: quindi, è l'ultimo elemento.
L' attuale implementazione fornita da SWI-Prolog , tuttavia, dice:
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:
L'ultimo elemento di un elenco ha senso solo per un elenco di almeno un elemento. L'ultimo elemento per una coppia di coda e la testa di un elenco, quindi, è: la testa, quando la coda è vuota, o l'ultimo della coda non vuota.
Il motivo per cui viene fornita questa implementazione è aggirare le questioni pratiche che circondano il modello esecutivo di Prolog. Idealmente, non dovrebbe fare la differenza sull'implementazione utilizzata. Allo stesso modo, avremmo potuto dire:
last(List, Last) :-
reverse(List, [Last|_]).
L'ultimo elemento di un elenco è il primo elemento dell'elenco invertito.
Se vuoi fare il pieno di discussioni inconcludenti su ciò che è buono, Prolog dichiarativo, basta passare attraverso alcune delle domande e risposte nel tag Prolog su Stack Overflow .