<beginning of page>
<beginning of page>
 3
 Sistema di controllo
Nel Capitolo 1 stato introdotto il concetto di backtracking ed il suo funzionamento stato
spiegato con esempi e tabelle. In questo capitolo verranno riassunti brevemente i punti
salienti per poi introdurre l'uso di una particolare funzione, detta cut che consente di
controllare il meccanismo di backtracking.
Supponiamo che sia in atto un tentativo di soddisfare un determinato goal che contenuto
nella parte destra di una regola. Si possono verificare due situazioni:
viene trovato nel database un fatto o una regola che corrisponde al goal; in questo caso
viene marcato quel punto del database e tutte le variabili contenute nel goal vengono is<hy>
tanziate; l'interprete ripeter poi questa procedura per il prossimo goal a destra, e cos via
fino ad esaurimento dei goal; non viene trovata nessuna corrispondenza fra quel goal ed il
contenuto del database; si dice allora che il goal fallisce; l'interprete cerca allora di risod<hy>
disfare i goal precedentemente considerati iniziando dal goal immediatamente prece<hy>
dente; in altre parole occorre disfare un po' di lavoro gi fatto per vedere se esistono altre
possibili soluzioni che consentano il soddisfacimento di tutti i goal componenti la parte
destra della regola.
Supponiamo ora di dover risoddisfare un goal. Occorre effettuare i seguenti passi: disis<hy>
tanziare tutte le variabili del goal; effettuare un'altra ricerca nel database a partire per dal
punto che era stato marcato al passo a).
Ovviamente, come nel caso precedente questo goal pu essere soddisfatto oppure fallire.
L'uso della funzione cut consente al programmatore di comunicare all'interprete quali
fra le scelte fatte in precedenza non necessario considerare per il risoddisfacimento. Le
principali ragioni che spingono ad utilizzare il cut sono: uno snellimento del programma
che risulta pi veloce in quanto non viene sprecato tempo ad elaborare fatti che difficil<hy>
mente potranno contribuire al raggiungimento di una soluzione; un risparmio di memoria
in quanto per i fatti da non riconsiderare non necessario effettuare l'operazione di mar<hy>
catura del database.
Il cut compare in una regola come un goal indicato con ! (punto esclamativo), ed ha la
propriet di essere sempre soddisfatto in quanto non ha argomenti. Inoltre ha l'effetto di
modificare il modo di operare del backtracking rimuovendo le marcature di alcuni goal in
modo che non possano essere risoddisfatti. Vediamone meglio il funzionamento con un
esempio molto semplice; supponiamo di voler andare in treno da Milano a Roma passan<hy>
do attraverso Genova, di voler partire subito dopo le 800 del mattino e di voler arrivare a
Roma prima delle 1900. Domandiamoci se possibile. Il database sia:
 puo_andare(A,B,C):-puo_partire(A,C,Ora_partenza_1,
   Ora_arrivo_1),
 Ora_partenza_1>800,!,   
puo_partire(C,B,
 Ora_partenza_2,Ora_arrivo_2),  
<beginning of page>
  Ora_partenza_2>Ora_arrivo_1,   
Ora_arrivo_2<1900.
 puo_partire(X,Y,T1,T2):-treno(X,Y,T1,T2).
 treno('Milano','Genova',700,900).
 treno('Milano','Genova',1100,1330).
 treno('Milano','Genova',830,1030).
 treno('Milano','Genova',1000,1230).
 treno('Genova','Roma',1200,1730).
 treno('Genova','Roma',1000,1600).
 treno('Genova','Roma',1400,2030).
 treno('Genova','Roma',1300,1930).
Vediamo qual il susseguirsi delle operazioni in seguito alla domanda:
 ?-puo_andare('Milano','Roma','Genova').
Le variabili A, B, C vengono istanziate rispettivamente a 'Milano', 'Roma', 'Geno<hy>
va'; l'interprete cerca poi di soddisfare:
 puo_partire(A,C,Ora_partenza_1,Ora_arrivo_1)
andando a cercare un treno che va da Milano a Genova. Il primo fatto che corrisponde
treno('Milano', 'Genova', 700, 900); Ora_partenza_1 viene istanzia<hy>
ta a 700 e Ora_arrivo_1 a 900. Il successivo predicato Ora_partenza_1>800
che non verificato; si instaura quindi un backtracking che porta alla disistanziazione di
Ora_partenza_1 e Ora_arrivo_1 ed alla loro successiva riistanziazione a 1100 e
1330. Il predicato Ora_partenza_1>800 ora verificato, il cut verificato sempre,
si passa quindi a valutare puo_partire(C, B, Ora_partenza_2, Ora_ar<hy>
rivo_2). Utilizzando il fatto treno('Genova', 'Roma', 1200, 1730)
Ora_partenza_2 istanziato a 1200 e Ora_arrivo_2 a 1730; il predicato succes<hy>
sivo non verificato perch 1200 non maggiore di 1330 il che fa ripartire il backtracking.
Dopo qualche passo viene selezionato il fatto che soddisfa il predicato Ora_parten<hy>
za_2>Ora_arrivo_1, con Ora_partenza_2=1400 e Ora_arrivo_2=2030.
Con questa scelta per non soddisfatto l'ultimo predicato e cio Ora_arrivo_2<1900.
Si instaura nuovamente il backtracking che risale indietro fino al cut in quanto non es<hy>
istono altre possibilit di soddisfare predicati a destra del cut stesso. Quando l'interprete
trova il cut come prossimo predicato a sinistra, l'analisi fallisce in quanto l'effetto del
cut quello di congelare le scelte fatte a monte di esso nell'ambito della regola che lo
contiene. Nel nostro esempio questo significa che Ora_partenza_1 rimane fissata a
1100 e Ora_arrivo_1 a 1330 e che quando il backtracking arriva al cut perch i val<hy>
ori 1100 e 1330 non soddisfano l'insieme dei goal nella regola, l'analisi fallisce nonos<hy>
tante che nel database esista un fatto (treno('Milano', 'Genova', 830,
1030)) che porterebbe al soddisfacimento dei goal. Mettere il cut in quella posizione
equivale a dire che una volta effettuata una scelta per andare da A a C, se non si riesce
con quella scelta ad arrivare fino a B, allora non c' niente da fare.
Gli usi pi comuni della funzione cut possono essere classificati come segue:
Si vuol comunicare al Prolog che stata trovata la strada giusta per soddisfare un certo
goal; il senso cio quello di dire all'interprete che se riuscito ad arrivare in quel partico<hy>
lare punto questo significa che sta utilizzando la regola giusta. Si vuole che un certo goal
fallisca immediatamente senza cercare soluzioni alternative; il cut viene in questo caso
usato insieme al predicato fail per dire all'interprete che quando arriva in quel partico<hy>
lare punto deve interrompere ogni tentativo di verificare quel goal. Si vuol interrompere
la generazione di soluzioni alternative mediante backtracking; con il cut si dice all'inter<hy>
prete che quando arriva in quel punto ha trovato l'unica soluzione che interessa ed quindi
inutile cercarne altre.
Illustriamo questi casi con degli esempi.
Supponiamo di voler calcolare il fattoriale di un numero N mediante una funzione
fact(N,X) che restituisce in X il risultato. Volendo definire a parole il funzionamento
della funzione fattoriale potremmo dire:
"il fattoriale di un numero N pari al prodotto di N per tutti i numeri che lo precedono
fino ad arrivare a 0; il fattoriale di 0 1." Da questa definizione appare chiaro che oc<hy>
corre distinguere due casi: N=0 e N>0. Occorrer quindi in Prolog scrivere due regole in
modo tale che il calcolo del fattoriale in base al prodotto dei numeri venga effettuato solo
nel caso che N sia diverso da 0. Il programma il seguente:
 fact(0,1):-!.
 fact(N,X):-M is N-1,fact(M,Y),X is Y*N.
<beginning of page>
In questo programma, la seconda regola , tra l'altro, un esempio di definizione ricorsiva.
Dal momento che l'interprete scorre il database dall'alto verso il basso, viene prima con<hy>
siderato il caso di N=0 e poi (solo se N>0) il caso generale codificato dalla seconda rego<hy>
la. Vediamo ora qual il significato del cut nella prima regola. Supponiamo che il Prolog
effettui un backtracking e vada a riconsiderare l'applicazione della regola fact nel caso
di N=0; la seconda regola risulta applicabile anche per N=0 a meno che la sua appli<hy>
cazione non venga inibita esplicitamente. Il cut nella prima regola ha proprio questo ef<hy>
fetto in quanto costringe l'interprete a mantenere per le variabili le scelte fatte durante
l'applicazione della prima regola. In queste situazioni, ovvero quando il suo uso ha lo
scopo di comunicare al sistema che stata trovata la regola giusta e che non devono essere
effettuati altri tentativi, il cut pu essere sostituito dal predicato not. not un predicato
di sistema (e quindi direttamente disponibile per l'utente) tale che il goal not(X) verifi<hy>
cato se X fallisce. A titolo di esempio riscriviamo il programma per il calcolo del fattori<hy>
ale utilizzando il predicato not.
 fact(0,1).
 fact(N,X):-not(N=0),M is N-1,fact(M,Y),X is Y*N.
In generale buona norma sostituire il cut in questo modo in quanto la leggibilit del pro<hy>
gramma diventa decisamente migliore. Alcune volte per la sostituzione pu causare un au<hy>
mento del carico computazionale perch l'interprete costretto a valutare pi volte lo stesso
goal, come, per esempio, nel caso delle seguenti regole:
 R:-goal1,goal2.
 R:-not(goal1),goal3.
in cui goal1 deve essere valutato due volte. Utilizzando il cut, invece si ha:
 R:-goal1,!,goal2.
 R:-goal3.
Vediamo ora l'uso del cut congiuntamente al predicato fail. Fail un predicato di sis<hy>
tema privo di argomenti ed definito in maniera tale che, considerato come goal, fallisce
sempre e causa pertanto l'inizio di un backtracking. Quando il fail viene incontrato
dopo un cut, per, l'azione del backtracking viene alterata. Illustriamo con un esempio il
significato di questo meccanismo. Supponiamo di voler codificare una serie di requisiti
per valutare l'attitudine di una persona per un certo lavoro e che requisito essenziale sia
un'et di almeno 30 anni.
 puo_lavorare(X):-eta(X,Y),Y<30,!,fail.
 puo_lavorare(X):-laureato(X),militeesente(X),
   eta(X,Y),Y<40.
 puo_lavorare(X):-media(X),pensionato(X).
 pensionato(X):-eta(X,Y),Y>65.
 militesente(X):-sesso(X,f).
 militesente(X):-congedato(X).
 eta('Rossi',25).
 eta('Bianchi',53).
 ...
Questo programmino codifica appunto una ricerca di personale che deve avere pi di 30
anni; nel caso abbia meno di 40 anni deve essere laureato e militesente; nel caso che sia
pensionato basta la licenza media. La prima regola in cui compare l'uso combinato di
cut e fail codifica il caso in cui l'et del candidato insufficiente. Supponiamo che l'et
sia 25; i primi due goal della prima regola sono soddisfatti, il cut soddisfatto, il fail
fallisce e fa partire il backtracking. Questo per si arresta immediatamente perch il cut ha
"congelato" le scelte precedenti, causando il fallimento del goal puo_lavorare. Se
non fosse stato introdotto il cut, il backtracking avrebbe proseguito con le regole suc<hy>
cessive; la seconda regola ad esempio, pu benissimo essere soddisfatta anche se l'et del
candidato 25 anni. In sintesi, questo meccanismo congiunto serve quando si conoscono
(e si riescono a codificare) in partenza situazioni in cui un determinato goal sicuramente
fallisce. Anche in questo caso il cut pu essere eliminato inserendo un not o un'altra
particolare condizione; la seconda regola pu essere, ad esempio, riscritta come segue
eliminando la prima:
 puo_lavorare(X):-laureato(X),militesente(X),
   eta(X,Y),Y>30,Y<40.
Il cut pu essere poi usato per arrestare il backtracking in quelle situazioni in cui, se
viene trovata una soluzione, occorre ignorare tutte le possibili alternative. Illustriamo
questo caso con il seguente programma che codifica una parte di un gioco in cui i gioca<hy>
tori devono, in certe situazioni, spostare un segnaposto da un punto ad un altro di un
tabellone che rappresenta una carta geografica.
 ...
<beginning of page>
 puo_spostarsi(Y,X,Z):-turno(Y),posizione(Y,X),
   strada_giusta(X,Z),!.
 strada_giusta(P1,P2):-strada(P1,P2),!.
 strada_giusta(P1,P2):-strada(P1,P3),   
strada_giusta(P3,P2).
 ...
Il senso il seguente:
"il giocatore Y pu spostarsi da X a Z se il suo turno di giocare, se nella posizione X e se
riesce a trovare la strada giusta da X a Z; la strada giusta fra due localit la strada che le
unisce direttamente oppure una strada che passa per un numero qualsiasi di localit inter<hy>
medie."
Il significato del primo cut quello di vietare al Prolog di andare a cercare, nel corso di
un eventuale backtracking generato da altre parti del programma, strade alternative che
congiungono X e Z; tali strade alternative possono ad esempio esistere se le due localit
non sono collegate direttamente. La motivazione potrebbe essere dettata dalle regole del
gioco che vietano ad un giocatore di cambiare strada una volta che ne sia stata scelta una.
Il secondo cut invece rientra nel caso 1: infatti, vieta al sistema di utilizzare la seconda
regola nel caso che esista un collegamento diretto fra due localit.
