1997
Remarque préliminaire : ce support de cours correspond à un
module de 20 heures, destiné à des étudiants en deuxième
année d'IUT. Ce cours n'est plus maintenu...
foncteur(t1, ..., tn)ou foncteur est une chaîne alpha-numérique commençant par une minuscule, et t1, ..., tn sont des termes (variables, termes élémentaires ou termes composés). Le nombre d'arguments n est appelé arité du terme.
Par exemple, adresse(18,"rue des lilas",Ville) est un terme composé de foncteur adresse et d'arité 3, dont les deux premiers arguments sont les termes élémentaires 18 et "rue des lilas" et le troisième argument est la variable Ville.
De même, cons(a,cons(X,nil)) est un terme composé de foncteur cons et d'arité 2, dont le premier argument est le terme élémentaire a et le deuxième argument le terme composé cons(X,nil).
symbole-de-prédicat(t1, ..., tn )ou symbole-de-prédicat est une chaîne alpha-numérique commençant par une minuscule, et t1, ..., tn sont des termes.
Par exemple,
pere(toto,paul)est une relation d'arité 2 entre les termes élémentaires toto et paul pouvant être interprétée par ``toto est le père de paul''.
De même,
habite(X,adresse(12,"rue r",nice))est une relation d'arité 2 entre la variable X et le terme composé adr(12,"rue r",nice) pouvant être interprétée par ``une personne inconnue X habite à l'adresse (12,"rue r",nice)''.
A.ou A est un atome logique, et signifie que la relation définie par A est vraie (sans condition). Par exemple, le fait
pere(toto,paul).indique que la relation ``toto est le père de paul '' est vraie.
Une variable dans un fait est quantifiée universellement. Par exemple, le fait
egal(X,X).indique que la relation ``X est égal à X '' est vraie pour toute valeur (tout terme) que X peut prendre.
A0 :- A1 , ... , An.ou A0, A1 , ... , An sont des atomes logiques. Une telle règle signifie que la relation A0 est vraie si les relations A 1 et ... et An sont vraies. A0 est appelé tête de clause et A1, ... , An est appelé corps de clause.
Une variable apparaissant dans la tête d'une règle (et éventuellement dans son corps) est quantifiée universellement. Une variable apparaissant dans le corps d'une clause mais pas dans sa tête est quantifiée existentiellement. Par exemple, la clause
meme_pere(X,Y) :- pere(P,X), pere(P,Y).se lit: ``pour tout X et pour tout Y, meme_pere(X,Y) est vrai s'il existe un P tel que pere(P,X) et pere(P,Y) soient vrais''.
Chaque paquet définit un prédicat et est constitué d'un ensemble de clauses dont l'atome de tête a le même symbole de prédicat et la même arité. L'ordre dans lequel les clauses sont définies est significatif. Intuitivement, deux clauses d'un même paquet sont liées par un ou logique. Par exemple, le prédicat personne défini par les deux clauses:
personne(X) :- homme(X).se lit ``pour tout X, personne(X) est vrai si homme(X) est vrai ou femme(X) est vrai''.
personne(X) :- femme(X).
?- pere(toto,X), pere(X,Y).se lit ``est-ce qu'il existe un X et un Y tels que pere(toto,X) et pere(X,Y) soient vrais''. La réponse de Prolog est l'ensemble des valeurs de X et Y qui vérifient cette relation. Autrement dit, la réponse de Prolog à cette question devrait être l'ensemble des enfants et petits-enfants de toto... si toto est effectivement grand-père.
s = { X <- Y, Z <- f(a,Y) }est la substitution qui ``remplace'' Xpar Y, Zpar f(a,Y), et laisse inchangée toute autre variable que Xet Z.
Par extension, une substitution peut être appliquée à un atome logique. Par exemple,
s(p(X,f(Y,Z))) = p(s(X),f(s(Y),s(Z))) = p(Y,f(Y,f(a,Y)))
s = { X <- a, Z <- f(a,Y) }est un unificateur de A1et A2car s(A1) = s(A2) = p(a,f(a,Y)).
s = { X <- Y }est un upg de p(X,b)et p(Y,b), tandis que
s' = { X <- a, Y <- a }n'est pas un upg de p(X,b)et p(Y,b).
L' algorithme de Robinson calcule un upg de deux termes, ou rend "echec" si les deux termes ne sont pas unifiables.
parent(paul,jean).L'ensemble des relations vraies sans condition dans P est E_0 :
parent(jean,anne).
parent(anne,marie).homme(paul).
homme(jean).pere(X,Y) :- parent(X,Y), homme(X).
grand_pere(X,Y) :- pere(X,Z), parent(Z,Y).
E_0 = { parent(paul,jean), parent(jean,anne), parent(anne,marie), homme(paul), homme(jean) }à partir de E_0 et P, on déduit l'ensemble des nouvelles relations vraies E_1:
E_1 = { pere(paul,jean), pere(jean,anne) }à partir de E_0, E_1 et P, on déduit l'ensemble des nouvelles relations vraies E_2:
E_2 = { grand_pere(paul,anne), grand_pere(jean,marie) }à partir de E_0, E_1, E_2 et P , on ne peut plus rien déduire de nouveau. L'union de E_0 , E_1 et E_2 constitue la dénotation (l'ensemble des conséquences logiques) de P.
Malheureusement, la dénotation d'un programme est souvent un ensemble infini et n'est donc pas calculable de façon finie. Considérons par exemple le programme P suivant:
plus(0,X,X).L'ensemble des atomes logiques vrais sans condition dans P est
plus(succ(X),Y,succ(Z)) :- plus(X,Y,Z).
E_0 = { plus(0,X,X) }à partir de E_0 et P, on déduit
E_1 = { plus(succ(0),X,succ(X)) }à partir de E_0, E_1 et P, on déduit
E_2 = { plus(succ(succ(0)),X,succ(succ(X))) }à partir de E_0, E_1, E_2 et P , on déduit
E_3 = { plus(succ(succ(succ(0))),X,succ(succ(succ(X)))) }etc ...
Pour prouver un but composé d'une suite d'atomes logiquesCe processus de preuve en chaînage arrière est résumé par la fonction prouver(But) suivante. Cette fonction affiche l'ensemble des instances de But qui font partie de la dénotation du programme:(par exemple, But = [A_1, A_2, .., A_n]),l'interprête Prolog commence par prouver le premier de ces atomes logiques (A_1). Pour cela, il cherche une clause dans le programme dont l'atome de tête s'unifie avec le premier atome logique à prouver(par exemple, la clause A'_0 :- A'_1, A'_2, ..,A'_rPuis l'interprête Prolog remplace le premier atome logique à prouver (A_1) dans le but par les atomes logiques du corps de la clause, en leur appliquant la substitution (s). La nouveau but à prouver devient
telle que upg(A_1,A'_0) = s)But = [s(A'_1), s(A'_2), .., s(A'_r), s(A_2), .., s(A_n)]L'interprête Prolog recommence alors ce processus, jusqu'à ce que le but à prouver soit vide, c'est à dire jusqu'à ce qu'il n'y ait plus rien à prouver. A ce moment, l'interprête Prolog a prouvé le but initial ; si le but initial comportait des variables, il affiche la valeur de ces variables obtenue en leur appliquant les substitutions successivement utilisées pour la preuveIl existe généralement plusieurs clauses dans le programme Prolog dont l'atome de tête s'unifie avec le premier atome logique à prouver. Ainsi, l'interprête Prolog va successivement répéter ce processus de preuve pour chacune des clauses candidates. Par conséquent, l'interprête Prolog peut trouver plusieurs réponses à un but.
procedure prouver(But: liste d'atomes logiques )Quand on pose une question à l'interprète Prolog, celui-ci exécute dynamiquement l'algorithme précédent. L'arbre constitué de l'ensemble des appels récursifs à la procédure prouver_bis est appelé arbre de recherche.
si But = [] alors
/* le but initial est prouvé */
/* afficher les valeurs des variables du but initial */
sinon soit But = [A_1, A_2, .., A_n]
pour toute clause (A'_0 :- A'_1, A'_2, ..,A'_r) du programme:
(ou les variables ont été renommées)
s <- upg(A_1 ,A'_0)
si s != echec alors
prouver([s(A'_1), s(A'_2), .. s(A'_r), s(A_2), .. s(A_n)], s(But-init}))
finsi
finpour
finsi
fin prouver
Une liste est une structure récursive: la liste Liste = [X|L] est composée d'un élément de tête X et d'une queue de liste L qui est elle-même une liste. Par conséquent, les relations Prolog qui ``manipulent'' les listes seront généralement définies par
La coupure est un prédicat sans signification logique (la coupure n'est ni vraie ni fausse), utilisée pour "couper" des branches de l'arbre de recherche.
La coupure est toujours "prouvée" avec succès dans la procédure prouver décrite au paragraphe 2.c. La "preuve" de la coupure a pour effet de bord de modifier l'arbre de recherche: elle coupe l'ensemble des branches en attente créées depuis l'appel de la clause qui a introduit la coupure.
Considérons par exemple le programme Prolog suivant:
q(a).
q(b).
r(a,a1).
r(a,a2).
r(b,b1).
r(b,b2).
r(b,b3).
b112 : s = {Y <- a2}
------ prouver([]) --> solution2 = {Z=a, T=a2}
b122 : s = {Y <- b2}
------ prouver([]) --> solution4 = {Z=b, T=b2}
b123 : s = {Y <- b3}
------ prouver([]) --> solution5 = {Z=b, T=b3}
si on définit p par
si on définit p par
Format peut contenir des séquences particulières
permettant d'afficher des caractères spéciaux:
Format peut également contenir des séquences particulières
pour inclure des éléments de la liste L. La séquence
dépend de la nature de l'élement à afficher:
Par exemple, l'exécution de
writef("ceci %t l'%s %t n et j'affiche X=%t",[est,"exemple numero",45,X])affichera la séquence:
ceci est l'exemple numero 45si la variable X a pour valeur toto.
et j'affiche X=toto