Anche dopo una terminazione di successo l'utente può forzare il sistema
ad eseguire un backtracking alla ricerca di altre soluzioni. Questo
avviene mediante il comando interattivo attivato dal carattere ";".
Esempio.
persona(paolo). persona(giulio). persona(luca). ?- persona(X). X = paolo; X = giulio; X = luca; no.
Esempio:
maschio(paolo). /fatto 1/
femmina(francesca). /fatto 2/
uomo(X) :- /regola 1/
persona(X).
persona(Y) :- /regola 2/
maschio(Y).
persona(Z) :- /regola 3/
femmina(Z).
?- uomo(francesca).
yes.
Cos'è avvenuto? Il Prolog quando cerca di soddisfare la regola 1 per
X = francesca
richiama la regola 2 che fallisce, poiché fallisce l'obiettivo
:- maschio(Y) con Y = francesca.
Esegue un backtracking e torna alla
regola 1, richiama la regola 3, istanzia X = Z = francesca e ha successo
con il fatto 2.
Il cut è uno speciale meccanismo che permette di dire, quando la procedura Prolog riprende la ricerca lungo la catena degli obiettivi soddisfatti (disistanziando le variabili che hanno portato al fallimento), quali delle scelte fatte in precedenza devono essere salvate e quali no.
Sintatticamente il cut compare nel corpo di una
regola come obiettivo indicato dal carattere "!" e ha sempre successo.
L'obiettivo che causa l'attivazione della clausola contenente il cut è
detto obiettivo padre; l'atomo selezionato nel padre può essere
unificato alla testa della clausola che contiene nel corpo il cut.
A :- B, C.
B :- D, !, E.
D.
?- A.
A, B, C, D, E sono atomi.
L'atomo selezionato B nell'obiettivo :- B, C
causa l'introduzione del cut. L'atomo D è selezionato e ha successo.
Poi il cut ha successo, ma l'atomo E fallisce e il sistema torna
indietro al cut. A questo punto il sistema non continua nessun'altra
ricerca nella regola 2) e, se ce ne fossero, prenderebbe in
considerazione un'altra possibilità per soddisfare l'obiettivo ?- A.
Esempio:
ha significato dichiarativoP :- A, !, B. P :- C.
P -> (A & B) v (not A & C),
mentre
ha significato dichiarativoP :- C. P :- A, !, B.
P -> C v (A & B).
Usando il cut si deve dunque prestare molta attenzione agli aspetti procedurali del programma.
consult fa leggere all'interprete il programma - costituito
da un insieme di clausole - contenuto in un file.
Le clausole lette vengono
inserite in coda alle clausole già presenti nel database.
Nel file possono anche essere presenti delle richieste all'interprete.
Queste
vengono eseguite immediatamente senza però fornire alcuna risposta sul
terminale.
L'argomento deve essere un atomo i cui caratteri devono soddisfare le specifiche sui nomi dei file sul particolare sistema. Se il nome del file contiene caratteri speciali, tutto il nome va scritto fra apici. Ad esempio sono corrette le seguenti richieste:
?- consult(file).
?- consult('File').
?- consult('file.p').
Questa procedura permette anche di aggiungere direttamente da terminale
nuove clausole al database.
Ciò è possibile specificando "user" come
argomento.
In tal caso l'interprete resta in attesa che le nuove
clausole vengano scritte dall'utente, che termina la scrittura con un
carattere di end_of_file.
La procedura reconsult è analoga alla procedura consult e per essa
valgono le stesse regole.
La differenza consiste nel fatto che le nuove
clausole lette dal file vengono sostituite e non aggiunte alle
clausole già presenti nel database aventi lo stesso predicato.
Questa
procedura è utile per la correzione di programmi, in quanto permette di
rileggere solo quei file che sono stati corretti senza ripartire da
capo.
arg(N,T,A)
arg(N,T,A) è usato per accedere a un particolare
argomento di un termine. I primi due argomenti (che vanno sempre
istanziati) specificano rispettivamente il numero dell'argomento e il
termine a cui ci si riferisce; il terzo argomento viene unificato con
l'argomento del termine richiesto.
atom(X)
atom(X) è usato per verificare se l'argomento X è un
atomo del Prolog.
atomic(X)
atomic(X) è usato per verificare se l'argomento X è un
atomo del Prolog (cfr. predicato atom) o un numero intero (cfr.
predicato integer).
functor(T,F,N)
functor(T,F,N) è usato in due possibili modi. Nel
primo, l'argomento T deve essere istanziato con un termine; in tal caso
F è unificato con il funtore principale di T, e N con un numero intero
indicante il numero di argomenti di T. Nel secondo modo, gli argomenti F
e N devono essere istanziati rispettivamente con un atomo e un numero
intero; in tal caso T è unificato con un termine avente F come funtore
principale e N argomenti variabili. Se però T è un termine atomico, F
e N sono unificati rispettivamente con T e con 0.
integer(N)
integer(N) è usato per verificare se l'argomento N è
istanziato con un numero intero.
name(A,L)
name(A,L) è usato per accedere ai caratteri che formano
un atomo. L è infatti la lista dei codici ASCII dei caratteri
dell'atomo A.
nonvar(X)
nonvar(X) è usato per verificare se l'argomento X è
istanziato con un termine che non è una variabile. Può essere
considerato il complementare del predicato var.
var(X)
var(X) è usato per testare se l'argomento X è istanziato
con una variabile.
=..
=.. (chiamato univ) serve a costruire o a scomporre delle
strutture. L'argomento L è una lista il cui primo elemento è il
funtore principale dell'argomento X e i cui altri elementi sono
nell'ordine gli argomenti di X.
asserta(C)
asserta(C) aggiunge in cima al database la nuova clausola
con cui è istanziato l'argomento C. Le variabili non istanziate nella
nuova clausola sono sostituite con nuove variabili interne.
assertz(C)
assertz(C) è analoga ad asserta, ma con la differenza che
la nuova clausola con cui è istanziato l'argomento C è aggiunta in
fondo al database.
call(X)
call(X) ha l'effetto di invocare come goal corrente il
termine con cui è istanziato l'argomento X e ha successo o fallisce se
e solo se questo goal ha successo o fallisce. Per tale ragione il
termine con cui è istanziato X deve essere interpretabile come goal.
Questa procedura è utile per introdurre dei goal variabili, soprattutto
in connessione col predicato =.. .
clause(P,Q)
clause(P,Q) è usata per accedere a delle clausole nel
database. Gli argomenti P e Q sono unificati rispettivamente con la
parte sinistra e la parte destra di una clausola presente nel database.
L'argomento P deve essere istanziato con un termine non variabile. Nel
caso di clausole unitarie (clausole senza parte destra), l'argomento Q
è unificato con il termine true.
listing(P)
listing(P) ha l'effetto di listare sul file di uscita
tutte le clausole presenti nel database il cui predicato della parte
sinistra è uguale all'atomo con cui deve essere istanziato l'argomento
P.
retract(X)
retract(X) ha l'effetto di cancellare dal database la
prima clausola che si unifica con l'argomento X, che deve essere
istanziato con un termine non variabile.
display(X)
display(X) scrive nell'uscita corrente il termine con cui
è istanziata X. Una struttura è scritta ignorando qualsiasi
dichiarazione di operatore, cioè con prima il funtore principale e poi
gli argomenti tra parentesi. Il goal display(X) ha successo solo una
volta.
get(X)
get(X) ha successo se il prossimo carattere scrivibile (con
codice ASCII maggiore di 32) nell'ingresso corrente si unifica con X. I
caratteri non scrivibili sono ignorati. Ha successo solo una volta. Il
risultato è l'unificazione di X con il carattere in ingresso.
get0(N)
get0(N) ha successo se N si unifica con il codice ASCII del
prossimo carattere nell'ingresso corrente. get0(N) ha successo solo una
volta, con il risultato di unificare N col carattere in ingresso.
nl
nl è sempre soddisfatto e provoca il passaggio alla linea di
scrittura successiva nell'uscita corrente.
print(X)
print(X) è usata per far scrivere sull'uscita corrente il
termine X con un formato specificato dall'utente, con una definizione
opportuna del predicato portray(X). print(X) si comporta come se fosse
definita nel modo seguente:
print(X) :- portray(X), !. print(X) :- write(X).
put(N)
put(N) ha successo solo una volta con il risultato di scrivere
sull'uscita corrente il carattere corrispondente al codice ASCII con cui
è istanziata N. Se N non è istanziata si ha un errore.
read(X)
read(X) ha successo solo una volta con il risultato di unificare
con il prossimo termine nell'ingresso corrente. Il termine deve essere
seguito da un punto "." e da almeno un carattere non scrivibile (con
codice ASCII minore o uguale a 32). Il punto finale viene eliminato. La
sintassi del termine deve soddisfare le dichiarazioni correnti di
operatori.
see(F)
see(F) definisce come ingresso corrente il file F. Si ha
errore se F non è istanziata o se il file specificato non esiste.
Normalmente l'ingresso corrente è user a cui è associato il terminale
dell'utente.
write(X)
write(X) scrive nell'uscita corrente il termine con cui è
istanziata X. Se X non è istanziata, scrive il codice della variabile
interna corrispondente . Una struttura viene scritta tenendo conto delle
dichiarazioni correnti di operatori, cioè se l'operatore è infisso
questo viene inserito fra gli argomenti. Il goal write(X) ha successo
solo una volta.
tell(X)
tell(X) definisce il file F come uscita corrente. La prima
volta che tell è invocata per un file non ancora aperto, questo viene
aperto e, se esso non esiste, viene creato; se invece esiste già, il
suo contenuto precedente viene distrutto. Si ha errore se F non è
istanziata.
telling(F)
telling(F) ha successo solo se F si unifica con il nome del file
di uscita corrente.
told(F)
told(F) chiude il file di uscita corrente inserendo un
carattere di fine file e ripristina user (il terminale dell'utente) come
uscita corrente.
Il goal
X is Y
ha successo solo se X si unifica con il risultato della
valutazione del termine con cui è istanziata Y. L'invocazione della
procedura is è l'unico modo per valutare una espressione aritmetica in
Prolog.
Le espressioni aritmetiche sono costruite con i seguenti operatori infissi:
X + Yper l'addizione; X - Yper la sottrazione; X * Yper la moltiplicazione; X / Yper la divisione intera; X mod Yper la valutazione del resto della divisione fra XeY.
I confronti fra valori numerici vengono effettuati con i seguenti goal, che usano operatori infissi corrispondenti a predicati sui numeri interi:
X=YX\=YX<YX>YX>=YX<=Y
spy P
spy P inserisce dei punti di controllo (spy point) in
corrispondenza delle procedure indicate nel parametro P. Se il parametro
P è un atomo, i punti di controllo sono inseriti in tutte le procedure
aventi tale atomo come nome, indipendentemente dal numero di parametri.
Se P è una struttura con un solo argomento numerico, i punti di
controllo sono inseriti in tutte le procedure aventi come nome il
funtore della struttura e numero di argomenti pari al valore
dell'argomento numerico della struttura. Se P è una lista, ogni
elemento della lista deve essere un argomento lecito per spy e i punti
di controllo sono inseriti in tutte le procedure specificate nella
lista. Ad esempio, direttive lecite sono le seguenti:
spy append spy max(2) spy [append,max(2)].
debugging
debugging lista i punti di controllo (spy point)
nell'uscita corrente.
nodebug
nodebug elimina tutti i punti di controllo (spy point)
correnti.
nospy P
nospy P elimina tutti i punti di controllo (spy point)
specificati nell'argomento P. L'argomento P ha lo stesso formato
specificato nella procedura spy.
trace
trace ha l'effetto di attivare il tracciamento esaustivo
della esecuzione del programma, che corrisponde alla scrittura di ogni
invocazione di procedura con i relativi valori correnti delle variabili.
notrace
notrace interrompe il tracciamento esaustivo
dell'esecuzione del programma. Continua soltanto il tracciamento dovuto
alla presenza di punti di controllo.
Gli elementi che rendono impuro il Prolog puro sono:
cut";