Prolog - fatti, regole, ricorsione, liste e cut
In questa pagina 11
Argomento del corso da 9 CFU (laboratorio 4). Fonte: slide del laboratorio di Intelligenza Artificiale, Ing. dell'Informazione UniPD, con le query provate su SWISH. Teoria alla base: Logica del primo ordine - unificazione, forward e backward chainingLogica del primo ordine (oggetti, relazioni, funzioni, quantificatori e errori tipici), sostituzioni, istanziazione universale ed esistenziale (Skolem), proposizionalizzazione e suoi limiti, unificazione con occur check, risoluzione nel primo ordine con conversione in CNF, esempio Criminal(West), clausole definite, forward e backward chaining, ingegneria della conoscenza.Logica del primo ordine - unificazione, forward e backward chaining →. I risultati delle query e le tracce sono stati verificati con un piccolo interprete scritto in Python (unificazione, ricerca in profondità, cut, is).
Cos'è Prolog
Prolog (PROgrammation en LOGique) è un linguaggio dichiarativo: si specifica cosa si vuole, non come ottenerlo; ci pensa il motore di inferenza.
- Approssima la logica del primo ordine ed è basato sulla dimostrazione automatica di teoremi; un programma è un insieme di clausole di Horn (disgiunzione di letterali con al più un letterale positivo).
- Manipola simboli, non numeri.
- Nato tra fine anni '60 e inizio anni '70 (1972 viene indicato come data di nascita) a Marsiglia e Edimburgo; primo compilatore nel 1977; standard ISO nel 1995. Usi: ragionamento a regole, linguaggio naturale, sistemi esperti, basi di dati con query complesse, didattica della logica.
- Implementazione usata: SWI-Prolog; SWISH è l'ambiente web in cui si scrive il programma (a sinistra) e si lanciano le query.
Va pensato in modo dichiarativo, non procedurale, ma conta come si scrive la conoscenza: l'ordine delle clausole e dei sotto-obiettivi cambia il comportamento (vedi più sotto).
Anatomia di un programma
Un programma Prolog è un insieme di clausole di Horn che rappresentano:
- fatti su oggetti e relazioni;
- regole (se... allora...) che definiscono oggetti e relazioni;
- goal (obiettivi): clausole senza testa, ciò che l'utente vuole sapere.
Ogni clausola finisce con un punto.
% regola: due persone sono colleghe se lavorano per la stessa azienda
colleague(X,Y) :- work(X,Z), work(Y,Z).
% fatti
work(emp1, deepmind).
work(emp2, deepmind).
work(emp3, ibm).
work(emp4, ibm).Nella regola la parte a sinistra di :- è la testa (head), quella a destra il corpo (body). Query (goal), a prompt ?-:
?- colleague(emp1, emp2). % true: lavorano entrambi per deepmind
?- colleague(emp1, emp3). % falseOperatori e traduzione in logica:
| Prolog | Logica | Significato |
|---|---|---|
A :- B |
implicazione, "A se B" | |
A , B |
congiunzione | |
A ; B |
disgiunzione |
- Fatto : se contiene variabili, ad esempio
likes(antonio, Thing)., vale per ogni valore: . Nell'esempio, Antonio ama qualunque cosa. - Regola
eats(Person, Thing) :- likes(Person, Thing), food(Thing).: per ogni persona e cosa, la persona mangia la cosa se la ama e la cosa è un cibo. Le variabili che compaiono solo nel corpo sono quantificate esistenzialmente:A(X) :- B(X, Y)significa . - Query
?- goal(X1,...,Xn).: "esistono valori che rendono il goal derivabile dal programma? Quali?". Prolog trova tutti i valori delle variabili, uno alla volta.
likes(mary, pizza).
likes(antonio, Thing).
likes(john, icecream).
food(pizza).
food(icecream).
eats(Person, Thing) :- likes(Person, Thing), food(Thing).
?- likes(john, pizza). % false
?- eats(john, X). % X = icecream
?- eats(antonio, X). % X = pizza ; X = icecreamUn fatto è una regola con corpo vero: likes(john, pizza). equivale a likes(john, pizza) :- true. L'insieme dei fatti e delle regole con la stessa testa (stesso nome e stessa arità) è la procedura pred.
Il motore di inferenza
Il motore usa unificazione e risoluzione (in pratica il backward chaining della logica del primo ordine). Esempio dal laboratorio:
parent(tommaso,francesca).
parent(tommaso,vittorio).
parent(francesca,linda).
parent(vittorio,bianca).
grampa(X,Y) :- parent(X,Z), parent(Z,Y).Query ?- grampa(tommaso, bianca). Il goal si unifica con la testa della regola: , . Il corpo diventa parent(tommaso,Z), parent(Z,bianca).
parent(tommaso,Z): la prima clausola applicabile èparent(tommaso,francesca), quindi (binding, legame).parent(francesca,bianca): nessun fatto: falso. Prolog fa backtracking: disfa il legame e cerca un'altra scelta perparent(tommaso,Z).parent(tommaso,vittorio), quindi ;parent(vittorio,bianca)è un fatto: vero. Rispostatrue.
Query con variabile ?- grampa(tommaso, Who). Who e Y sono la stessa variabile. Albero di ricerca (search tree, o albero SLD della risoluzione di Prolog: radice il goal, un nodo per ogni scelta di clausola, foglie di successo o fallimento):
?- grampa(tommaso, Who)
| clausola 5, {X = tommaso, Y = Who}
?- parent(tommaso, Z), parent(Z, Who)
|- parent(tommaso, francesca) {Z = francesca}
| ?- parent(francesca, Who)
| |- parent(francesca, linda) {Who = linda} SUCCESSO: Who = linda
|- parent(tommaso, vittorio) {Z = vittorio} (dopo backtracking)
?- parent(vittorio, Who)
|- parent(vittorio, bianca) {Who = bianca} SUCCESSO: Who = biancaSWISH risponde Who = linda e, se si chiede un'altra soluzione (;), Who = bianca. Le due risposte sono le foglie di successo.
Esempio di backtracking con due sotto-obiettivi.
likes(mary,food). % clausola 1
likes(mary,wine). % clausola 2
likes(john,beer). % clausola 3
likes(john,wine). % clausola 4
?- likes(mary,X), likes(john,X). % X = winelikes(mary,X) dà (clausola 1); likes(john,food) fallisce, quindi si ritorna indietro e likes(mary,X) dà (clausola 2); likes(john,wine) è la clausola 4: successo con . In generale, trovato un insieme di legami che soddisfa goal_1, Prolog vi si impegna e prova goal_2; se goal_2 riesce cerca soluzioni extra, se fallisce cerca altri legami per goal_1. In entrambi i casi ritorna indietro.
Strategia di ricerca: in profondità (come in Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →, ma con ritorno all'ultima scelta):
- le clausole del programma si considerano dall'alto in basso;
- i sotto-obiettivi nel corpo da sinistra a destra;
- se una scelta si rivela sbagliata si torna indietro.
La ricerca in profondità non è completa: Prolog può percorrere un ramo infinito senza trovare la soluzione anche se esiste. Contano quindi l'ordine delle clausole e quello dei sotto-obiettivi. Regole pratiche: in una procedura prima i fatti, poi le regole; nel corpo prima gli obiettivi più facili da provare o confutare.
Esempio di ramo infinito. Con gli stessi fatti di parent, aggiunti parent(linda, carlo).:
anc(X,Y) :- parent(X,Y).
anc(X,Y) :- parent(X,Z), anc(Z,Y). % ricorsione a destra: OK
?- anc(tommaso, Y). % francesca, vittorio, linda, carlo, bianca
anc2(X,Y) :- anc2(X,Z), parent(Z,Y). % ricorsione a SINISTRA: richiama se stessa
anc2(X,Y) :- parent(X,Y). % subito, senza consumare nulla: ramo infinitoanc2(tommaso, carlo) richiama anc2(tommaso, Z) con gli stessi argomenti, all'infinito (nel nostro interprete, RecursionError; in SWI-Prolog, errore di stack). La versione a destra consuma un parent a ogni chiamata e termina: se il grafo è aciclico si arriva sempre ai fatti.
Per seguire l'esecuzione c'è il comando trace: mostra ogni goal eseguito, se riesce e i passi di backtracking.
Termini
Fatti, regole e query sono fatti di termini.
- Atomi:
valentino,valentino_rossi,valentino46,'hello world'(con apici). Iniziano per minuscola. - Numeri:
12,-34,345.01,0.328. - Variabili: iniziano per maiuscola o
_:X,Who,_variable. Il simbolo_è la variabile "non mi interessa" (anonima): ogni_è diverso dagli altri. - Termini composti (strutture): un funtore (atomo) applicato ad argomenti, a loro volta termini:
book('Le tigri di Mompracem', author(emilio, salgari)),father(father(antonio)),son(X,Y) :- parent(Y,X).
Non c'è differenza sintattica tra fatti, regole, goal e strutture usabili come argomenti: una sequenza di goal è una struttura con , come funtore principale, una clausola una struttura con :-. Prolog è tipizzato dinamicamente; si può interrogare il tipo:
| Predicato | È vero se il termine è | Esempi dalle slide |
|---|---|---|
var(T) |
una variabile non istanziata | var(X) true; X = 5, var(X) false |
nonvar(T) |
non è una variabile libera | nonvar(X) false; X = 5, nonvar(X) true |
integer(T), float(T) |
intero, virgola mobile | integer(5) true; integer(5.0) false |
number(T) |
intero o float | number(5) e number(5.0) true; number(atom) false |
atom(T) |
un atomo (i numeri non sono atomi) | atom(hello) e atom('hello world') true; atom('123') true (apici: è testo) |
atomic(T) |
atomo o numero | atomic(5) true; atomic(f(x)) false |
(Il caso X = Y, var(X), var(Y) è vero: dopo aver unificato due variabili libere restano entrambe libere, ma la stessa.)
Unificazione
L'operatore = significa unifica: due termini unificano se e solo se sono identici o possono essere resi identici legando variabili a termini. Le regole sono quelle viste in Logica del primo ordine - unificazione, forward e backward chainingLogica del primo ordine (oggetti, relazioni, funzioni, quantificatori e errori tipici), sostituzioni, istanziazione universale ed esistenziale (Skolem), proposizionalizzazione e suoi limiti, unificazione con occur check, risoluzione nel primo ordine con conversione in CNF, esempio Criminal(West), clausole definite, forward e backward chaining, ingegneria della conoscenza.Logica del primo ordine - unificazione, forward e backward chaining →:
- costante con costante: unificano se sono la stessa;
- variabile con qualsiasi termine: si lega;
- termine composto con termine composto: stesso funtore, stessa arità e argomenti che unificano a coppie;
- ogni altro caso fallisce.
?- tommaso = tommaso. % true
?- tommaso = X. % X = tommaso
?- grampa(X,Y) = grampa(bianca, Z). % X = bianca, Y = Z
?- f(X,b) = f(a,Y). % X = a, Y = b
?- f(X,X) = f(a,b). % false: X non può essere a e b
?- 3+2 = 5. % false: 3+2 è la struttura +(3,2), non il numero 5Occur check. Si può unificare con ? Richiederebbe . Il controllo dice: una variabile si lega a un termine solo se non vi compare. Prolog non lo fa per default (per velocità): X = father(X) "riesce" creando un termine ciclico. Per il controllo si usa unify_with_occurs_check(T1, T2): unify_with_occurs_check(X, father(X)) è false.
Confronti utili: = unifica (può legare variabili); \= riesce se non unificano; == controlla identità senza legare (X == Y è false se sono variabili diverse).
Aritmetica
= non calcola. Per valutare un'espressione aritmetica si usa is:
?- 3+2 = X. % X = 3+2 (solo unificazione: nessun calcolo)
?- X is 3+2. % X = 5
?- 5 is 3+2. % true
?- X is 3.0 + 2.0. % X = 5.0
?- 5 is 3 + X. % errore: Arguments are not sufficiently instantiatedA destra di is l'espressione deve essere completamente istanziata: is non risolve equazioni. I confronti <, >, =<, >=, =:= (uguale come valore), =\= valutano entrambi i lati: 7 =:= 3+4 è true, mentre 7 == 3+4 è false.
Ricorsione
Come in RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →: un caso base e un passo che riduce il problema. Fattoriale:
factorial(0,1).
factorial(N,L) :- N>0, N1 is N-1, factorial(N1,L1), L is N*L1.
?- factorial(6,Result). % Result = 720Esecuzione di factorial(3,L) (verificata):
3>0,N1 is 3-1(), si chiamafactorial(2,L1);2>0, ,factorial(1,L2);1>0, ,factorial(0,L3);factorial(0,L3)si unifica col fatto: ;- si risale: , , .
Il controllo N>0 serve: senza, quando si chiede un'altra soluzione dopo factorial(0,1) si prova anche la seconda clausola con , che richiama factorial(-1, ...) e non termina mai. Con N>0 il ramo fallisce subito.
Liste
Una lista è una sequenza finita di elementi: [antonio, vittorio, tommaso], [antonio, 2, 3.0], [antonio, father(antonio), X, Y], [] (vuota), [antonio, [2, [b,c]], f(X), []] (elementi di qualsiasi tipo, anche liste).
Una lista non vuota si divide in testa (head, il primo elemento) e coda (tail, la lista senza il primo). L'operatore | scompone una lista:
?- [Head|Tail] = [antonio, vittorio, tommaso].
% Head = antonio, Tail = [vittorio, tommaso]
?- [Head|Tail] = [antonio, [2,[b,c]], f(X), []].
% Head = antonio, Tail = [[2,[b,c]], f(X), []]
?- [Testa|Coda] = []. % false: la lista vuota non ha testa
?- [One, Two|Remaining] = [antonio, [2,[b,c]], f(X), []].
% One = antonio, Two = [2,[b,c]], Remaining = [f(X), []]
?- [One, Two|Remaining] = [antonio, 2, [b,c], f(X), []].
% One = antonio, Two = 2, Remaining = [[b,c], f(X), []]La coda è sempre una lista (anche []); la testa può essere qualsiasi termine. [a,b] è la notazione breve di [a|[b|[]]].
length(?List, ?Int) è vero se Int è il numero di elementi:
?- length([a,b,c], 3). % true
?- length([], 0). % true
?- length([pippo,[a,b]], 3). % false: ha 2 elementi (la sotto-lista [a,b] conta uno)append(?List1, ?List2, ?List1AndList2) è vero se la terza lista è la concatenazione delle prime due. Si può usare in tutte le direzioni (i ? indicano che ogni argomento può essere input o output):
?- append([a,b], [1,2,3], X). % X = [a,b,1,2,3]
?- append(X, [1,2,3], [a,b,1,2,3]). % X = [a,b]
?- append(X, Y, [1,2,3]).
% X = [], Y = [1,2,3] ;
% X = [1], Y = [2,3] ;
% X = [1,2], Y = [3] ;
% X = [1,2,3], Y = []L'ultima query elenca le possibili divisioni di una lista di elementi (in generale ). La stessa definizione serve per tutti gli usi perché è relazionale:
append([], L, L). % caso base: [] davanti non cambia L
append([H|T], L, [H|R]) :- append(T, L, R). % la testa H passa nel risultatoEsempio append([a,b],[1],X) (verificato): la regola ricorsiva con chiama append([b],[1],R1), poi con chiama append([],[1],R2), che per il fatto dà ; risalendo e .
Altre definizioni nello stesso stile, tutte testate sull'interprete: member(X,[X|_]). con member(X,[_|T]) :- member(X,T).; sumlist([],0). con sumlist([H|T],S) :- sumlist(T,S1), S is S1+H. (sumlist([3,5,2],S) dà S = 10); last([X],X). con last([_|T],X) :- last(T,X). In ogni procedura sulle liste: un caso base per [] (o per la lista di un elemento) e un passo che scompone [H|T].
Il cut
Cut (!) è un predicato senza argomenti che controlla il backtracking. Riesce sempre (dal punto di vista dichiarativo equivale a true) ma ha un effetto collaterale: quando viene eseguito, impegna Prolog sulle scelte fatte da quando il goal padre è stato chiamato. Cioè elimina le alternative rimaste aperte da quando il goal padre (quello la cui clausola contiene il cut) si è unificato con la testa della regola, compresa la scelta di usare quella clausola.
Esempio: perché Vittorio non è sano. Programma senza cut:
antibodies_owner(maria).
dance(linda). sing(linda). sing(gianni).
noproblems(vittorio).
healthy(X) :- happy(X).
healthy(X) :- antibodies_owner(X).
happy(X) :- sing(X), dance(X).
happy(X) :- noproblems(X).
?- healthy(X). % X = linda ; X = vittorio ; X = mariaOrdine di esecuzione: healthy(X) clausola 1 → happy(X) clausola 1: sing(linda), dance(linda) riesce: . Ritorno: sing(gianni), dance(gianni) fallisce. happy(X) clausola 2: noproblems(vittorio): . Poi healthy clausola 2: antibodies_owner(maria): .
Con il cut dopo sing(X):
happy(X) :- sing(X), !, dance(X).
happy(X) :- noproblems(X).
?- healthy(X). % X = linda ; X = mariaCon il cut viene eseguito e dopo di esso Prolog non può più ritornare né su sing(X) (niente gianni) né sulla seconda clausola di happy (niente vittorio): happy(X) ha solo la soluzione linda. Resta healthy clausola 2, con maria. Quindi la query aperta healthy(X) non restituisce Vittorio, come dicono le slide. Attenzione però: ?- healthy(vittorio). risponde ancora true (sing(vittorio) fallisce prima del cut, che non viene mai raggiunto, e si passa alla seconda clausola): il cut ha reso il programma non reversibile, cioè dipendente dall'uso che se ne fa.
Cut verde e cut rosso.
- Cut verde: cambia l'efficienza ma non il significato (con o senza, le soluzioni sono le stesse).
min(X,Y,X) :- X =< Y, !.
min(X,Y,Y) :- Y < X, !.Le due condizioni sono già mutuamente esclusive: il cut serve solo a non provare la seconda clausola dopo la prima. min(3,5,M) dà , min(5,3,M) dà .
- Cut rosso: cambia il significato. Esempio, il programma dei sani sopra:
healthy(X)perdevittorio. Un altro: si toglie la condizione dalla seconda clausola, contando sul cut.
min2(X,Y,X) :- X =< Y, !.
min2(X,Y,Y).min2(3,5,M) e min2(5,3,M) danno ancora il minimo. Ma min2(3,5,5) risponde true (sbagliato): la prima clausola non si unifica con la testa (X = 3, ma terzo argomento 5), il cut non viene raggiunto e la seconda clausola, senza condizione, riesce. Qui il programma funziona solo quando il terzo argomento è libero: ha un significato procedurale, non dichiarativo.
Cut e fail. !, fail fa fallire subito l'obiettivo, senza provare le clausole successive della stessa procedura (che in assenza di cut verrebbero provate). Si usa per le eccezioni: "tutti gli uccelli volano, tranne pinguino e struzzo".
bird(nightingale). bird(blackbird).
penguin(king_penguin). ostrich(camelus_ostrich).
fly(X) :- penguin(X), !, fail.
fly(X) :- ostrich(X), !, fail.
fly(X) :- bird(X).
?- fly(blackbird). % true
?- fly(king_penguin). % false
?- fly(camelus_ostrich). % false
?- fly(X). % false <-- ATTENZIONECon fly(king_penguin) la clausola 1 vale (penguin(king_penguin)), il cut elimina la clausola 2 e la 3, fail fa fallire tutto: false. Con la query aperta fly(X) il primo legame è (primo fatto di penguin): cut, fail, e non si prova la clausola 3, quindi la risposta è false anche se nightingale vola. Ancora una volta il programma funziona solo con l'argomento istanziato.
Nota sul programma delle slide: king_penguin e camelus_ostrich non sono dichiarati bird, quindi fly(king_penguin) fallirebbe anche senza le clausole di eccezione (la clausola 3 non avrebbe bird(king_penguin)). Le eccezioni hanno senso, e si vede la differenza, se i pinguini sono anche uccelli, aggiungendo bird(king_penguin). e bird(camelus_ostrich).: i risultati delle query qui sopra restano gli stessi, ma ora è il cut-fail a impedire fly(king_penguin).
Negazione come fallimento
Cut-fail dà una forma di negazione, la negazione come fallimento (negation as failure): not(G) riesce se non si riesce a dimostrare G, e fallisce se G si dimostra (in SWI-Prolog anche \+ G). Si riscrive fly:
bird(nightingale). bird(blackbird).
penguin(king_penguin). ostrich(camelus_ostrich).
fly(X) :- bird(X), not(penguin(X)), not(ostrich(X)).
?- fly(blackbird). % true
?- fly(king_penguin). % false
?- fly(camelus_ostrich). % false
?- fly(X). % X = nightingale ; X = blackbirdIl programma è più leggibile e fly(X) ora enumera gli uccelli che volano (verificato). (Come sopra, nelle slide i pinguini non sono bird: aggiungendo bird(king_penguin). e bird(camelus_ostrich). i risultati restano gli stessi e a escluderli sono i due not.) Ma non è la negazione logica: not(G) vuol dire "G non è derivabile dal programma" (ipotesi del mondo chiuso: ciò che non è nel programma è falso). Inoltre l'ordine conta: not va dopo i goal che istanziano le variabili. Con
bird(nightingale). bird(king_penguin). penguin(king_penguin).
fly(X) :- not(penguin(X)), bird(X).
fly2(X) :- bird(X), not(penguin(X)).?- fly(X). risponde false: not(penguin(X)) con X libera prova penguin(X), che riesce (X = king_penguin), quindi il not fallisce prima di aver legato X a un uccello. fly2(X) dà X = nightingale.
Prolog e logica
- Una clausola
A :- B1, ..., Bnè la clausola di Horn ; un fatto ha corpo vuoto. L'esecuzione è backward chaining in profondità con unificazione, quindi si applicano i limiti già visti: non completa (rami infiniti), senza occur check per default. - Non tutto è logica:
ise i confronti aritmetici richiedono argomenti istanziati, il cut dipende dall'ordine,notè un fallimento e non una negazione. Per questo le clausole vanno scritte pensando anche a come verranno eseguite. - La ricerca di Prolog è la ricerca in profondità di Ricerca nello spazio degli stati - formulazione dei problemi e ricerca non informataUn agente risolutore di problemi è un agente basato su obiettivi con rappresentazione atomica: formula un problema (stato iniziale, azioni, modello di transizione, test dell'obiettivo, costo dei passi) e cerca una sequenza di azioni che porta a uno stato obiettivo costruendo un albero di ricerca. Le strategie non informate usano solo la definizione del problema e si distinguono per l'ordine di espansione della frontiera: ampiezza (coda FIFO), costo uniforme (coda con priorità sul costo g), profondità (pila LIFO), profondità limitata, approfondimento iterativo e ricerca bidirezionale. Si confrontano per completezza, ottimalità, tempo e spazio in funzione di b, d, m; tutte sono casi di best-first search con una diversa funzione di valutazione f.Ricerca nello spazio degli stati - formulazione dei problemi e ricerca non informata →, con lo spazio degli stati dato dalle clausole. Le liste di Prolog sono immutabili, a differenza di Liste in Pythonlist come sequenza mutabile (array dinamico); indici, slicing e assegnamento a fette; metodi e loro costo; aliasing, copia superficiale e profonda; list comprehension; liste annidate e matrici.Liste in Python →: non si modificano, se ne costruiscono di nuove per unificazione.
Esempi svolti: Esercizio - Prolog - fatti, regole e alberi SLD (laboratorio 4), Esercizio - Prolog - aritmetica, ricorsione e liste (laboratorio 4), Esercizio - Prolog - cut e negazione come fallimento (laboratorio 4).
Domande d'esame
- Risultato di
?- [H|T] = [a].e di?- [H|T] = [].Il primo dàH = a, T = [](la coda di una lista di un elemento è la lista vuota); il secondofalse(la lista vuota non ha testa). X is 3+2, Y = 3+2cosa dà? E3+2 = 5?X = 5(iscalcola) eY = 3+2(=unifica soltanto,Yè la struttura+(3,2));3+2 = 5èfalseperché+(3,2)e5non unificano.- Con
happy(X) :- sing(X), !, dance(X).quali soluzioni dàhealthy(X)? Ehealthy(vittorio)?healthy(X)dàlindaemaria;healthy(vittorio)ètrueperchésing(vittorio)fallisce prima del cut e si prova la seconda clausola dihappy. Il cut è rosso. - Perché con le clausole cut-fail
?- fly(X).rispondefalseanche senightingaleè un uccello? Il primo legame èX = king_penguin; la prima clausola esegue il cut efail, e il cut impedisce di provare la clausola 3.
Versione ripasso
- Prolog: dichiarativo, programma = clausole di Horn, base nella logica del primo ordine (Logica del primo ordine - unificazione, forward e backward chainingLogica del primo ordine (oggetti, relazioni, funzioni, quantificatori e errori tipici), sostituzioni, istanziazione universale ed esistenziale (Skolem), proposizionalizzazione e suoi limiti, unificazione con occur check, risoluzione nel primo ordine con conversione in CNF, esempio Criminal(West), clausole definite, forward e backward chaining, ingegneria della conoscenza.Logica del primo ordine - unificazione, forward e backward chaining →). Ogni clausola finisce con
.. Fatti, regole (testa :- corpo), goal (?- ...).,= ,;= ,A :- B= . - Significato logico: fatto con variabili = ; variabili solo nel corpo = (
A(X) :- B(X,Y)= ). Un fatto è una regola con corpotrue. Procedura = clausole con stessa testa (nome e arità). Query = ricerca di tutti i valori delle variabili. - Motore: backward chaining in profondità: clausole dall'alto in basso, sotto-obiettivi da sinistra a destra, backtracking sulle scelte sbagliate. Non completo: rami infiniti (ricorsione a sinistra
anc2(X,Y) :- anc2(X,Z), parent(Z,Y)). Regole: prima i fatti, poi le regole; prima gli obiettivi facili. - Esempio
grampa(X,Y) :- parent(X,Z), parent(Z,Y):grampa(tommaso,Who)prova (dà ), poi (dà ).likes(mary,X), likes(john,X): fallisce, riesce. - Termini: atomi (minuscola), numeri, variabili (maiuscola o
_), compostifuntore(arg,...). Test di tipo:var,nonvar,integer,float,number,atom(i numeri non sono atomi),atomic. - Unificazione
=: identici o rendibili tali con legami;f(X,X) = f(a,b)false;3+2 = 5false. Occur check: Prolog non lo fa;unify_with_occurs_check(X, father(X))è false. Altri:\=non unifica,==identità senza legare. - Aritmetica:
X is 3+2dà (3+2 = XdàX = 3+2);5 is 3 + Xerrore, a destra diistutto istanziato. Confronti<,>,=<,>=,=:=,=\=. - Ricorsione:
factorial(0,1).factorial(N,L) :- N>0, N1 is N-1, factorial(N1,L1), L is N*L1.factorial(6,R)dà ; senzaN>0non termina al backtracking. - Liste:
[H|T]testa e coda (la coda è una lista);[]non ha testa;[One,Two|Rest].length(L,N).append([],L,L).append([H|T],L,[H|R]) :- append(T,L,R).Usabile in ogni direzione:append(X,Y,[1,2,3])dà divisioni. - Cut
!: riesce sempre, elimina le alternative dal goal padre in poi (anche le altre clausole). Verde: non cambia il significato (mincon=<e<). Rosso: lo cambia (healthy(X)consing(X), !, dance(X)perdevittorio, mahealthy(vittorio)ètrue;min2(3,5,5)ètrueper errore). - Cut e fail:
!, failfa fallire subito senza provare le clausole successive.fly(X)con cut-fail rispondefalseperché il primo legame èking_penguin. - Negazione come fallimento:
not(G)riesce seGnon è dimostrabile (mondo chiuso).fly(X) :- bird(X), not(penguin(X)), not(ostrich(X)).dànightingaleeblackbird.notva dopo i goal che istanziano le variabili. - Programmi di riferimento:
grampa(X,Y) :- parent(X,Z), parent(Z,Y). % Z = francesca poi Z = vittorio
factorial(0,1).
factorial(N,L) :- N>0, N1 is N-1, factorial(N1,L1), L is N*L1.
append([], L, L).
append([H|T], L, [H|R]) :- append(T, L, R).
happy(X) :- sing(X), !, dance(X). % cut rosso: perde vittorio
fly(X) :- penguin(X), !, fail. % cut-fail: eccezione
fly(X) :- bird(X), not(penguin(X)), not(ostrich(X)). % negazione come fallimento- Tracce verificate:
grampa(tommaso,bianca): ,parent(francesca,bianca)fallisce, , riesce.likes(mary,X), likes(john,X): fallisce, riesce.factorial(3,L): , poi , , , .append([a,b],[1],X): . - Clausola di Horn:
A :- B1,...,Bn= ; fatto = corpo vuoto. Esecuzione = backward chaining in profondità con unificazione. Albero SLD: radice il goal, un ramo per clausola applicabile, foglie di successo o fallimento. - Cut, riassunto: eseguito, impegna sulle scelte da quando il goal padre è stato unificato con la testa (anche sulle clausole successive). Verde = stesse soluzioni; rosso = soluzioni diverse; non reversibile (
healthy(X)perde vittorio mahealthy(vittorio)è true). - Query sui tipi (dalle slide):
var(X)true;X = 5, var(X)false;X = 5, nonvar(X)true;integer(5.0)false;number(5.0)true;atom('hello world')true;atomic(f(x))false. - Query su unificazione e liste:
grampa(X,Y) = grampa(bianca,Z)dàX = bianca, Y = Z;[H|T] = [antonio,[2,[b,c]],f(X),[]]dàH = antonio,T = [[2,[b,c]],f(X),[]];[One,Two|Rest] = [antonio,2,[b,c],f(X),[]]dàOne = antonio, Two = 2;length([pippo,[a,b]],3)false;append(X,[1,2,3],[a,b,1,2,3])dàX = [a,b]. - Ordine di esecuzione di
healthy(X)(senza cut):linda(sing e dance),giannifallisce sudance, poivittorio(noproblems), poimaria(antibodies_owner). Con!doposing(X): sololindaemaria. - Punto di attenzione:
fly(X)con cut-fail rispondefalsecon variabile libera (primo legameking_penguin, cut,fail). Nelle slide i pinguini non sonobird: aggiungendoli le risposte non cambiano. - Errori tipici: ricorsione a sinistra;
=al posto diis;iscon variabile a destra; cut messo male (rosso involontario);notsu variabile libera; dimenticareN>0.