In generale il grado di nondeterminismismo di un certo atomo nel goal (ossia il numero di figli che si ottendgono con un passo di risoluzione se selezionamo tale atomo) dipende da quanto e' instanziato il goal.
p(a)e consideriamo il goal <- p(x),q(x). Se usiamo la regola di selezione leftmost, otterremo un albero con due rami, uno di successo (con cas {x/a}), ed uno infinito. Se avessimo usato la regola di selezione rightmost, invece, avremmo avuto un abero finito e addirittura con un solo ramo (ovviamente di successo e con la stessa cas). Il motivo e' che la computazione di <- p(a) e' meno nondeterministica di quella di <- p(x), in quanto per risolvere <- p(a) possiamo usare solo la prima clausola, mentre per risolvere <- p(x) possiamo usare anche la seconda, e andare in ciclo.
p(b) <- p(b)
q(a)
In generale, sarebbe conveniente avere una regola di selezione piu' flessibile di quella del Prolog. Una regola che permetta di scegliere un atomo solo quando e' "abbastanza instanziato" e che riduca cosi' il grado di nondeterminimo e quindi la complessita' della ricerca delle soluzioni.
A tale scopo, nelle versioni di Prolog piu' moderne, quali il NU-Prolog, e' stata aggiunta la possibilita' di inserire in un programma le cosiddette dichiarazioni di delay. Una dichiarazione di delay e' un'espressione della forma:
DELAY p(x1,...,xn) UNTIL Cond(x1,...,xn)dove p e' un predicato del programma, e Cond(x1,...,xn) una certa condizione logica.
Il significato di tale dichiarazione e' quello di modificare la regola di selezione (e quindi la semantica operazionale) del Prolog nel seguente modo:
Seleziona l'atomo p(t1,...,tn) piu' a sinistra tale che per ogni dichiarazione di delay della forma DELAY p(x1,...,xn) UNTIL Cond(x1,...,xn), vale che Cond(t1,...,tn) e' verificata.Gli atomi piu' a sinistra dell'atomo selezionato con questa nuova regola, ossia quelli per i quali le corrispondenti condizioni non erano soddisfatte, si dicono sospesi o delayed. Essi rimangono nel goal, ma potranno essere selezionati in qualche passo successivo per effetto delle istanziazioni che si sono avute nel frattempo.
DELAY p(x) UNTIL nonvar(x)allora la nuova regola di selezione da' luogo ad un albero finito e con un solo ramo, che, in questo particolare caso, e' lo stesso che si sarebbe avuto con la regola rightmost.
Analogamente, con le dichiarazioni di delay si puo' perdere la proprieta' di invertibilita':
DELAY p(x,y) UNTIL nonvar(x)allora in generale non potremo piu' usare tale predicato con la modalita' di output sul primo argomento.
Comunque, se usate in modo oculato, in Prolog le dichiarazioni di delay permettono addirittura di recuperare parte della completezza che si perde a causa della strategia depth-first del Prolog. Consideriamo il seguente esempio:
perm(L1,L2)
(L2
e' una permutazione di
L2
) e il predicato ausiliario
delete(X,L1,L2)
(L2
e' la lista che si ottiene
eliminando X
da
L1
)
perm([],[]).
perm([X|L1],L2) :- delete(X,L2,L3), perm(L1,L3).
delete(X,[X|L],L).
delete(X,[Y|L1],[Y|L2]) :- delete(X,L1,L2).
L'interprete del Prolog standard, con la query
?- perm(L,[a,b]).
,
genera entrambe le risposte
L =[a,b]
,
L =[b,a]
,
e poi termina.
Se diamo invece la query
?- perm([a,b],L).
,
allora l'interprete genera la risposta
L =[a,b]
,
e poi va in ciclo.
Il problema e' che con la seconda query si arriva ad un certo punto a
valutare un goal della forma
?- delete(b,L',L"),
perm([],L").
.
Con la regola di valutazione leftmost, si cercano prima tutte le
possibili soluzioni per
delete(b,L',L")
,
e quindi si va in ciclo sulla quarta clausola. Notare che
in tale ciclo si generano infinite soluzioni,
di cui solo la prima va bene, cioe' e' compatibile con le soluzioni di
perm([],L")
.
Se avessimo usato una dichiarazione di delay del tipo
DELAY delete(X,L1,L2) UNTIL nonvar(L1) OR nonvar(L2)
allora anche con la query
?- perm([a,b],L).
l'interprete avrebbe generato entrambe le risposte
L =[a,b]
,
L =[b,a]
,
e poi avrebbe terminato.
DELAY perm(L1,L2) UNTIL nonvar(L1) OR nonvar(L2)
DELAY delete(X,L1,L2) UNTIL nonvar(L1) OR nonvar(L2)