Introduction à PROLOG

Christine Solnon

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...

  1. Syntaxe et terminologie Prolog
  2. Signification (sémantique) d'un programme Prolog
  3. Les Listes
  4. La coupure
  5. Quelques prédicats prédéfinis de SWI-Prolog
  6. Sujets de TP
 Quelques cours Prolog sur le Web : Swi-Prolog : http://www.swi-prolog.org/

1. Syntaxe et terminologie Prolog

Un programme Prolog est constitué d'un ensemble de clauses ; une clause est une affirmation portant sur des atomes logiques ; un atome logique exprime une relation entre des termes ; les termes sont les objets de l'univers. Dans ce chapitre, on va successivement définir ce qu'est un terme , un atome logique, une clause et un programme Prolog.

1.a. Les termes

Les objets manipulés par un programme Prolog (les "données" du programme) sont appelés des termes. On distingue trois sortes de termes:


1.b. Les relations, ou atomes logiques

Un atome logique exprime une relation entre des termes ; cette relation peut être vraie ou fausse. Syntaxiquement, un atome logique est de la forme:
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.
Le nombre d'arguments n est appelé arité de l'atome logique.

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)''.
 

1.c. Les clauses

Une clause est une affirmation inconditionnelle (un fait) ou conditionnelle (une règle).


1.d. Les programmes Prolog

Un programme Prolog est constitué d'une suite de clauses regroupées en paquets. L'ordre dans lequel les paquets sont définis n'est pas significatif.

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).
personne(X) :- femme(X).
se lit ``pour tout X, personne(X) est vrai si homme(X) est vrai ou femme(X) est vrai''.
 

1.e. Exécution de programmes Prolog

``Exécuter'' un programme Prolog consiste à poser une question à l'interprète PROLOG. Une question (ou but ou activant) est une suite d'atomes logiques séparés par des virgules. La réponse de Prolog est ``yes'' si la question est une conséquence logique du programme, ou ``no'' si la question n'est pas une conséquence logique du programme. Une question peut comporter des variables, quantifiées existentiellement. La réponse de Prolog est alors l'ensemble des valeurs des variables pour lesquelles la question est une conséquence logique du programme. Par exemple, la question
?- 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.


2. Signification (sémantique) d'un programme Prolog

2.a Définitions préliminaires

Substitution :
Une substitution (notée s) est une fonction de l'ensemble des variables dans l'ensemble des termes. Par exemple,
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)))
Instance :
Une instance d'un atome logique A est le résultat s(A) de l'application d'une substitution s sur A.

Par exemple, pere(toto,paul)est une instance de pere(toto,X).
 
Unificateur :
Un unificateur de deux atomes logiques A1 et A2 est une substitution s telle que s(A1) = s(A2).

Par exemple, soit A1 = p(X,f(X,Y)), et soit A2 = p(a,Z),
s = { X <- a, Z <- f(a,Y) }
est un unificateur de A1et A2car s(A1) = s(A2) = p(a,f(a,Y)).
 
Unificateur plus général :
Un unificateur sde deux atomes logiques A1 et A2 est le plus général (upg) si pour tout autre unificateur s' de A1 et A2, il existe une autre substitution s'' telle que s' = s''(s).

Par exemple,
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.


2.b. Dénotation d'un programme Prolog

La dénotation d'un programme Prolog P est l'ensemble des atomes logiques qui sont des conséquences logiques de P. Ainsi, la réponse de Prolog à une question est l'ensemble des instances de cette question qui font partie de la dénotation. Cet ensemble peut être "calculé" par une approche ascendante, dite en chaînage avant: on part des faits (autrement dit des relations qui sont vraies sans condition), et on applique itérativement toutes les règles conditionnelles pour déduire de nouvelles relations ... jusqu'à ce qu'on ait tout déduit. Considérons par exemple le programme Prolog suivant:
parent(paul,jean).
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).

L'ensemble des relations vraies sans condition dans P est E_0 :
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).
plus(succ(X),Y,succ(Z)) :- plus(X,Y,Z).
L'ensemble des atomes logiques vrais sans condition dans P est
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 ...
 

2.c. Signification opérationnelle

D'une façon générale, on ne peut pas calculer l'ensemble des conséquences logiques d'un programme par l'approche ascendante: ce calcul serait trop couteux, voire infini. En revanche, on peut démontrer qu'un but (composé d'une suite d'atomes logiques) est une conséquence logique du programme, en utilisant une approche descendante, dite en chaînage arrière:
Pour prouver un but composé d'une suite d'atomes logiques
(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'_r
telle que upg(A_1,A'_0) = s)
Puis 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
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 preuve

Il 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.

Ce 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: 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.

Remarques



3. Les listes

La liste est un terme composé particulier de symbole de fonction ``.'' et d'arité 2: le premier argument est l'élément de tête de la liste, et le deuxième argument est la queue de la liste. La liste vide est notée ``[]''.

Notations:

Par exemple, la liste [a,b,c] correspond à la liste .(a,.(b,.(c,[]))) et contient les 3 éléments a , b et c. La liste [a,b|L] correspond à la liste .(a,.(b,L)) et contient les 2 éléments a et b , suivis de la liste (inconnue) L.

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


4. La coupure

4.a. Signification opérationnelle de la coupure

La coupure, aussi appelée "cut", est notée !.

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:

L'arbre de recherche construit par Prolog pour le but p(Z,T) est: En fonction de l'endroit ou l'on place une coupure dans la définition de p, cet arbre de recherche est plus ou moins élagué, et certaines solutions supprimées : Exercice : que répond Prolog aux questions suivantes ?
  1. ?- element(L,[[a,b],[c,d,e]]), element(X,L).
  2. ?- element(L,[[a,b],[c,d,e]]), element(X,L), !.
  3. ?- element(L,[[a,b],[c,d,e]]), !, element(X,L).
  4. ?- !, element(L,[[a,b],[c,d,e]]), element(X,L).

4.b. Les applications de la coupure


5. Quelques prédicats prédéfinis de SWI-Prolog

Edition et chargement de programmes:

Comparaison de termes:

Arithmétique:

réussit si le résultat de l'évaluation de l'expression arithmétique Expr1 est égal au résultat de l'évaluation de l'expression arithmétique Expr2, unifie le résultat de l'évaluation de l'expression arithmétique Expr avec la variable Var.

Entrées/Sorties:


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 tex2html_wrap_inline636 n et j'affiche X=%t",[est,"exemple numero",45,X])
affichera la séquence:
ceci est l'exemple numero 45
et j'affiche X=toto
si la variable X a pour valeur toto.