Michel Gagnon
École Polytechnique de Montréal
4 Opérateurs arithmétiques 23
4.1 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
1
5 Listes 25
5.1 Définitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2 Unification de listes . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6 Processus récursif 28
6.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
6.2 Vérification de l’appartenance à une liste . . . . . . . . . . . . . . 29
6.3 Concaténation de listes . . . . . . . . . . . . . . . . . . . . . . . 34
6.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
12 Mécanismes de contrôle 72
12.1 Coupe-choix : . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
12.2 Dangers avec le coupe-choix . . . . . . . . . . . . . . . . . . . . 81
12.3 Négation : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
12.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
2
14 Conseils pour bien programmer en Prolog 87
14.1 Erreurs fréquentes . . . . . . . . . . . . . . . . . . . . . . . . . . 87
14.2 Trace d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . 88
3
1.2 Definition de prédicats
Pour définir des relations en Prolog, on utilise des prédicats. Un prédicat est
une expression qui retourne une valeur de vérité quand elle est appliquée à certains
arguments (les arguments d’un prédicat sont des termes). Si un prédicat accepte
seulement un argument, on dit qu’il définit une propriété. Par exemple, l’expression
intelligent(michel) représente une proposition qui sera vraie si Michel est
intelligent, et fausse dans le cas contraire.
Si un prédicat a plus d’un argument, il exprime une relation entre des entités du
monde. Par exemple, on peut écrire une expression mere(michel, marie_berthe)
pour indiquer que Marie-Berthe est la mère de Michel.
L’argument d’un prédicat peut être n’importe quel terme. Il peut être un terme
composé, comme dans acheter(laura, appartement(145, edifice(a))).
Il peut aussi être une variable, comme dans mere(X, marie_berthe). Cette
dernière expression peut être comprise de la manière suivante : Marie-Berthe est la
mère de quelqu’un, mais on ne sait pas qui.
Nous savons déjà qu’une expression prédicative, c’est-à-dire un prédicat avec
ses arguments, représente une propriété ou une relation qui peut être vraie ou
fausse. Mais comment déterminer cette valeur de vérité ? Une manière simple de
le faire consiste à déclarer un fait. Ainsi, en Prolog, pour indiquer que Michel
est intelligent, on ajoutera le fait suivant (notez l’usage du point qui clôt la décla-
ration) :
intelligent(michel).
Un ensemble de faits est le programme le plus simple que nous pouvons écrire
en Prolog. Voici un exemple de programme :
homme(paulo).
homme(joao).
homme(francisco).
femme(maria).
femme(roberta).
femme(ana).
pere(paulo,francisco).
pere(paulo,joao).
pere(francisco,ana).
mere(roberta,rita).
mere(roberta,joao).
mere(roberta,francisco).
mere(maria,roberta).
4
Remarquez que pere n’est pas un foncteur, mais plutôt un prédicat. Il n’est
pas utilisé ici comme un fonction qui désigne le père de quelqu’un. Quand nous
écrivons pere(francisco,paulo), nous indiquons que la relation pere est
vraie dans le cas de Francisco et Paulo. Qui est le père de qui dépend du pro-
grammeur. L’ordre des arguments n’a pas d’importance en Prolog. L’important est
d’être consistant dans l’usage qu’on fait d’un prédicat. Si le programmeur écrit
pere(francisco,paulo) pour indiquer que le premier est le père du second,
et non le contraire, il devra utiliser cette convention à tous les endroits où ce pré-
dicat sera utilisé. Ici, nous utilisons la convention où l’entité représentée par le
premier argument est le père de celle exprimée par le second argument. Ainsi, dans
notre exemple, Paulo est le père de Francisco. Idem pour la relation mère.
1.3 Requêtes
En Prolog, on utilise un programme en lui soumettant des requêtes. Une re-
quête est n’importe quelle expression prédicative entrée après l’invite ?-. Prolog
répondra Yes si elle est vraie, et No dans le cas contraire. Avec le programme
simple présenté plus haut, on peut faire diverses requêtes. On peut vérifier si Maria
est une femme, ou encore si João est le père de Paulo :
?- femme(maria).
Yes
?- pere(joao,paulo).
No
Une requête peut contenir des variables. Pour comprendre ce qui se produit
dans ce cas, il faut d’abord définir deux concepts importants :
5
Normalement, une substitution est appliquée à un terme composé ou une ex-
pression prédicative. L’effet de l’application d’une substitution θ à une expression
A, dénotée θA, est le suivant : pour toute variable X présente dans A, on rem-
place toutes les occurences de celle-ci par le terme t qui lui est associé dans θ. Par
exemple, la substitution {X = paulo, Y = 2}, appliquée à l’expression p(X,f(Z),Y),
résulte en une nouvelle expression p(paulo,f(Z),2). Voici d’autres exemples :
θ A θA
{X = paulo, Y= 2} p(X,X,maria) p(paulo,paulo,maria)
{X = paulo, Y = 2} p(X,Z) p(paulo,Z)
{X = paulo, Y = q(Z)} p(fun(Y),X,r(X),W) p(fun(q(Z)),paulo,r(paulo),W)
{X = Y} p(X) p(Y)
Instance : Une expression A est une instance d’une autre expression B s’il
existe une substitution θ telle que A = θB. Par exemple, p(paulo,Y) est
une instance de p(X,Y). De même, p(paulo,maria) est une instance de
p(X,Y).
?- homme(X).
?- homme(X).
X = paulo
Une fois cette première réponse donnée, Prolog attend une directive. Si nous en-
trons <enter>, il s’arrêtera et répondra YES :
?- homme(X).
X = paulo
Yes
?- homme(X).
X = paulo ;
X = joao
6
Si encore une fois nous entrons ’ ;’ il retourne la troisième réponse possible :
?- homme(X).
X = paulo ;
X = joao ;
X = francisco
Si nous le faisons encore une fois, il répondra No, puisqu’il n’y a pas d’autres
possibilités :
?- homme(X).
X = paulo ;
X = joao ;
X = francisco ;
No
Il est possible de construire une requête qui contient plus d’une expression pré-
dicative (dans le jargon de Prolog, on dit d’une telle expression prédicative qu’elle
est un but). Tous les buts d’une requête sont combinés en utilisant l’opérateur vir-
gule. Par exemple, pour identifier un fils de Roberta et Paulo, nous ferions la re-
quête suivante :
?- mere(roberta,X),pere(paulo,X).
X = joao ;
X = francisco ;
No
1.4 Clauses
Si nous avions seulement la possibilité d’écrire des faits, Prolog ne serait pas
très utile. Normalement, la determination d’une relation entre deux entités se fait
de manière indirecte. Par exemple, une personne est propriétaire d’un objet si elle
a acheté cet objet. Dans ce cas, nous utilisons une clause, qui définit un prédicat en
fonction d’autres prédicats :
proprietaire(X,Y):-
acheter(X,Y).
Cette clause doit être comprise de la manière suivante : pour savoir si X est le
propritétaire de Y, il faut vérifier si X a acheté Y. Il existe une autre manière d’être
propriétaire d’un objet : le recevoir. Ainsi, notre programme peut être étendu pour
tenir compte de cette seconde possibilité :
7
proprietaire(X,Y):-
acheter(X,Y).
proprietaire(X,Y):-
recevoir(X,Y).
Pour être réellement utile, ce programme doit être complété par certains faits :
acheter(maria,manteau).
recevoir(paulo,auto).
proprietaire(X,Y):-
acheter(X,Y).
proprietaire(X,Y):-
recevoir(X,Y).
?- proprietaire(paulo,auto).
Yes
?- proprietaire(paulo,manteau).
No
?- proprietaire(maria,X).
X = manteau
?- proprietaire(X,Y).
X = maria
Y = manteau ;
X = paulo
Y = auto ;
No
?- proprietaire(X,X).
No
Remarquez que la dernière requête échoue parce que les deux arguments doivent
être instanciés par le même terme.
8
Rien n’empêche de mélanger des faits avec des clauses. Par exemple, dans le
programme, on peut spécifier directement que Ana est propriétaire d’un chien qui
s’appelle Berto :
acheter(maria,manteau).
recevoir(paulo,auto).
chien(berto).
proprietaire(ana,berto).
proprietaire(X,Y):-
acheter(X,Y).
proprietaire(X,Y):-
recevoir(X,Y).
Attention : Il ne faut pas confondre ’prédicat’, qui est associé à une valeur de
vérité, et ’terme composé’, qui désigne une entité du monde.
1.5 Exercices
1.1 Soit le programme suivant :
situe_dans(curitiba,parana).
situe_dans(florianopolis,santa_catarina).
situe_dans(joinville,santa_catarina).
situe_dans(montreal,quebec).
situe_dans(X,bresil):-
situe_dans(X,parana).
situe_dans(X,bresil):-
situe_dans(X,santa_catarina).
situe_dans(X,canada):-
situe_dans(X,quebec).
situe_dans(X,amerique):-
situe_dans(X,bresil).
situe_dans(X,amerique):-
situe_dans(X,canada).
Quelle est la réponse aux requêtes suivantes ? (S’il y en a plus d’une, donnez-
les toutes)
?- situe_dans(florianopolis,santa_catarina).
9
?- situe_dans(florianopolis,parana).
?- situe_dans(X,santa_catarina).
?- situe_dans(curitiba,X).
?- situe_dans(X,Y).
1.2 Écrivez les clauses Prolog qui définissent les relations suivantes :
?- aime(joao,X) = aime(Y,mere_de(Y)).
X = mere_de(joao)
Y = joao
10
L’autre détail important est illustré par l’exemple suivant d’unification, qui peut
retourner plus d’un résultat :
aime(joao,X) = aime(Y,Z)
{ Y = joao, X = Z }
ou
{ Y = joao, X = Z, W = francisco }
ou
{ Y = joao, X = joao, Z = joao }
ou
...
Ce que l’interprète tente de faire est une unification qui retourne l’unificateur
le plus général, c’est-à-dire celui qui n’instanciera une variable que si c’est réelle-
ment nécessaire de le faire pour obtenir l’unificateur.
Voyons maintenant un algorithme d’unification qui retourne l’unificateur le
plus général. Soient A et B les deux expressions (prédicatives ou fonctionnelle)
à unifier. Voici l’algorithme qui retourne un unificateur θ, s’il existe, et retourne
ECHEC sinon :
θ=∅
La pile contient l’équation A = B
Tant que pile non vide :
Retirer la première équation X = Y de la pile
Si :
X est une variable qui n’apparaît pas dans Y :
Substituer toutes les occurrences de X par Y dans la pile et dans θ
Ajouter X = Y dans θ
Y est une variable qui n’apparaît pas dans X :
Substituer toutes les occurrences de Y par X dans la pile et dans θ
Ajouter Y = X dans θ
X et Y sont des constantes ou des variables identiques :
Continuer
X est f (X1 , ...Xn ) et Y est f (Y1 , ...Yn ) :
Empiler les n équations Xi = Yi
Aucun de ces cas :
Retourner ECHEC
Retourner θ
11
Intuitivement, deux expressions prédicatives A et B s’unifient si elles sont for-
mées à partir d’un même prédicat, avec le même nombre d’arguments. Chaque
argument du prédicat de A doit s’unifier avec l’argument à la même position dans
B. Rappelons-nous que chaque argument est lui-même un terme, qui peut être une
variable, une constante ou un terme composé. Deux termes s’unifient si une des
trois conditions suivantes est respectée :
homme(paulo).
homme(joao).
homme(francisco).
femme(maria).
femme(roberta).
femme(ana).
pere(paulo,francisco).
pere(paulo,joao).
pere(francisco,ana).
mere(roberta,rita).
mere(roberta,joao).
mere(roberta,francisco).
mere(maria,roberta).
Pour savoir qui est une femme, il suffit de faire la requête suivante :
?- femme(X).
X = maria
12
différents. Il en sera de même pour les deux faits qui suivent. C’est seulement
lorsque l’interprète rencontre le fait femme(maria) que l’unification réussit.
Dans ce cas, le résultat de l’unification est une instance de la variable X avec la
constante maria. Voici d’autres exemples :
?- pere(paulo,X).
X = francisco ;
X = joao ;
No
?- pere(X,Y).
X = paulo
Y = francisco ;
X = paulo
Y = joao ;
X = francisco
Y = ana ;
No
?- pere(paulo,_).
Yes
Deux occurrences d’une variable anonyme dans un prédicat sont distinctes. Par
exemple, si nous désirons seulement savoir s’il existe une mère, nous soumettrons
la requête suivante :
?- mere(_,_).
Yes
Il est clair ici qu’il ne s’agit pas de trouver toute personne qui est sa propre
mère. En d’autres mots, cette requête n’est pas équivalente à celle-ci :
13
?- mere(X,X).
No
aime(paulo,maria).
aime(ana,clara).
aime(claudia,paulo).
Si nous voulons spécifier que tout le monde aime Maria, nous ajouterions le
fait suivant, qui contient une variable anonyme :
aime(_,maria).
Si nous voulons spécifier que tout le monde s’aime soi-même, nous ajouterions
le fait suivant :
aime(X,X).
?- X = 1.
X = 1
?- joao = roberta.
No
?- suc(X) = suc(suc(suc(0))).
X = suc(suc(0))
?- p(X,X) = p(1,2).
No
14
?- p(X,X) = p(1,1).
X = 1
?- p(X,X) = p(1,W).
X = 1
W = 1
?- p(Y,fun(Y)) = p(toto,Z).
Y = toto
Z = fun(toto)
?- p(X,X,2) = p(1,W,W).
No
Pour s’assurer que deux termes ne sont pas unifiables, on utilise l’opérateur \= :
?- p(X,X,2) \= p(1,W,W).
Yes
2.5 Exercices
2.1 Quel est le résultat des tentatives d’unification suivantes :
UFPR = ’UFPR’
ufpr = ’UFPR’
ufpr = ’ufpr’
4 = 3+1
f(X,a(b,c)) = f(Z,a(Z,c))
f(X,a(b,X)) = f(Z,a(Z,c))
population(ville((curitiba),X) = population(ville((Z),K)
population(ville((curitiba),X) = population(W,ville((montreal))
suc(suc(X)) = suc(suc(suc(suc(Z))))
(habite(X,app(145,edifice(K))),proprietaire(paulo,app(Z))) = (habite(M,A),proprietaire(M,A))
(habite(X,app(145,edifice(K))),proprietaire(paulo,Z)) = (habite(M,A),proprietaire(M,A))
(habite(X,app(145,edifice(K))),proprietaire(paulo,Z)) =
(habite(M,app(W,edifice(b123))),proprietaire(M,A))
(habite(X,app(145,Z)),proprietaire(paulo,app(145,Z))) =
(habite(M,app(W,edifice(b123))),proprietaire(M,A))
15
3 Algorithme d’exécution d’un programme
3.1 Algorithme de résolution
Essentiellement, un programme Prolog est composé de règles, qui sont des
clauses déclarant un fait qui dépend d’autres faits. Ces règles ont la forme suivante :
H :- B1 ,B2 , . . .,Bn
16
3.2 Exemple d’exécution
Soit le programme suivant (rappelons-nous qu’on doit interpréter le prédicat
pere de telle manière que le premier argument représente le père de la personne
indiquée par le second argument, et similairement pour le prédicat mere) :
homme(paulo).
homme(joao).
homme(francisco).
femme(maria).
femme(roberta).
femme(ana).
pere(paulo,francisco).
pere(paulo,joao).
pere(francisco,ana).
mere(roberta,rita).
mere(roberta,joao).
mere(roberta,francisco).
mere(maria,roberta).
parents(X,M,P):-
mere(M,X),
pere(P,X).
frere(X,Y):-
homme(Y),
parents(X,M,P),
parents(Y,M,P).
Nous allons maintenant voir en détail comment est résolue la requête suivante :
?- frere(joao,francisco).
Yes
Au début la résolvante ne contient que la requête, qui sera unifiée avec la tête
de la dernière clause du programme. Ceci entraînera l’ajout des trois buts suivants
dans la résolvante :
homme(francisco)
parents(joao,M,P)
parents(francisco,M,P)
Le premier but, qui s’unifie avec un des faits du programme, est alors retiré.
Le prochain but s’unifie avec la tête de l’avant-dernière clause du programme. En
17
renommant M1 et P1 les deux variables de la clause, l’unificateur est le suivant :
{M = M1, P = P1}. Les buts du corps de la clause seront alors ajoutés dans la
résolvante :
mere(M1,joao)
pere(P1,joao)
parents(francisco,M1,P1)
parents(francisco,roberta,paulo)
Remarquez ici que les variables du but on été instanciées, à cause de la résolu-
tion des deux buts précédents. Rappelons-nous que nous avions déja M = M1. Or,
comme M1 a été instanciée par la constante roberta, la variable M est automati-
quement instanciée par la même constante. Idem pour la variable P.
Pour résoudre ce dernier but, on l’unifie avec l’avant-dernière clause du pro-
gramme, et obtient alors les deux buts suivants :
mere(roberta,francisco)
pere(paulo,francisco)
Finalement, ces deux derniers buts sont résolus de manière triviale, puisqu’il
existe, pour chacun, un fait identique dans le programme. La résolution se termine
donc avec succès, avec la substitution suivante : M = roberta, P = paulo.
Voici la résolution complète de cette requête (nous indiquons, à chaque étape,
la substitution θ) :
[frere(joao,francisco)] {}
[homme(francisco),parents(joao,M,P),parents(francisco,M,P)] {}
[parents(joao,M1,P1),parents(francisco,M1,P1)] {M=M1, P=P1}
[mere(M1,joao),pere(P1,joao),parents(francisco,M1,P1)] {M=M1, P=P1}
[pere(P1,joao),parents(francisco,roberta,P1)] {M=M1=roberta, P=P1}
[parents(francisco,roberta,paulo)] {M=M1=roberta, P=P1=paulo}
[mere(roberta,francisco),pere(paulo,francisco)] {M=M1=roberta, P=P1=paulo}
[pere(paulo,francisco)] {M=M1=roberta, P=P1=paulo}
[]
18
?- frere(francisco,Z).
X = joao
[frere(francisco,Z)] {}
*[homme(Y),parents(francisco,M,P),parents(Y,M,P)]} {Z=Y}
[parents(francisco,M,P),parents(paulo,M,P)] {Z=paulo}
[mere(M,francisco),pere(P,francisco),parents(paulo,M,P)] {Z=paulo}
[pere(P,francisco),parents(paulo,roberta,P)] {Z=paulo}
[parents(paulo,roberta,paulo)] {Z=paulo}
[mere(roberta,paulo),pere(paulo,paulo)] {Z=paulo}
ÉCHEC
Quand cet échec se produit, il faut reculer au dernier point de choix, indiqué par
une astérisque. Dans ce cas, il s’agit du moment où était tentée la résolution du but
homme(Y). Reculant à ce point, et en poursuivant la recherche d’une clause s’uni-
fiant à ce but, on obtient une nouvelle instanciation (Y = joao), et la résolution
continue à partir de ce point
[frere(francisco,Z)] {}
*[homme(Y),parents(francisco,M,P),parents(Y,M,P)]} {Z=joao}
[parents(joao,M,P),parents(joao,M,P)] {Z=joao}
[mere(M,francisco),pere(P,francisco),parents(joao,M,P)] {Z=joao}
[pere(P,francisco),parents(joao,roberta,P)] {Z=joao}
[parents(joao,roberta,paulo)] {Z=joao}
[mere(roberta,joao),pere(paulo,joao)] {Z=joao}
[pere(paulo,joao)] {Z=joao}
[] {Z=joao}
Maintenant, nous sommes en mesure de comprendre l’effet du " ;" entré après
une première solution retournée. Il s’agit en fait d’une demande de retour arrière,
tout comme si la résolution s’était butée à un échec. Dans le dernier exemple, le
résultat serait le suivant :
?- frere(francisco,X).
X = joao ;
X = francisco ;
No
Pour fournir cette seconde solution, l’interprète Prolog recule jusqu’au dernier
point de choix, qui correspond toujours au moment où on a traité but homme(Y) :
19
[frere(francisco,Z)] {}
[homme(Y),parents(francisco,M,P),parents(Y,M,P)]} {Z=francisco}
[parents(francisco,M,P),parents(francisco,M,P)] {Z=francisco}
[mere(M,francisco),pere(P,francisco),parents(francisco,M,P)] {Z=francisco}
[pere(P,francisco),parents(francisco,roberta,P)] {Z=francisco}
[parents(francisco,roberta,paulo)] {Z=francisco}
[mere(roberta,francisco),pere(paulo,francisco)] {Z=francisco}
[pere(paulo,francisco)] {Z=francisco}
[] {Z=francisco}
On voit que cette technique est pratique pour connaître, dans cet exemple, tous
les frères d’une personne. Mais on voit aussi que notre programme a le défaut
de nous indiquer que chaque homme est le frère de soi-même. Pour éviter cela,
il faudrait modifier la clause qui définit le prédicat frere, en ajoutant un test
supplémentaire :
frere(X,Y) :-
homme(Y),
parents(X,M,P),
parents(Y,M,P),
X \= Y.
L’ordre des buts dans le corps de la clause est très important dans l’exécution
d’un programme. Supposons, par exemple, que le prédicat frere soit défini de la
manière suivante :
frere(X,Y) :-
parents(X,M,P),
parents(Y,M,P),
homme(Y),
X \= Y.
Dans ce cas, beaucoup d’unifications inutiles seraient tentées. Avec une requête
comme frere(francisco,X), le programme pourrait tenter inutilement de
résoudre le but parents(Y,M,P) en instanciant Y par une valeur qui représente
une femme.
20
tres_intelligent(X):-
intelligent(X),
sympathique(X).
tres_intelligent(X):-
professeur_ia(X).
intelligent(X):-
debrouillard(X),
humain(X).
intelligent(X):-
dauphin(X).
humain(X):-
homme(X).
humain(X):-
femme(X).
professeur_ia(michel).
debrouillard(chico).
dauphin(flipper).
femme(george).
homme(chico).
sympathique(flipper).
?- tres_intelligent(X).
Pour résoudre ce but, l’interpréteur Prolog devra réaliser plusieurs retours ar-
rière. Voici le déroulement de la résolution jusqu’au premier échec :
*[tres_intelligent(X)] {}
*[intelligent(X),sympathique(X)] {}
[debrouillard(X),humain(X),sympathique(X)] {}
*[humain(chico),sympathique(chico)] {X=chico}
[homme(chico),sympathique(chico)] {X=chico}
[sympathique(chico)] {X=chico}
Échec
21
Par conséquent, le point de choix auquel on reculera, au premier échec, est
celui associé au but humain(X). La résolution repart à partir de ce point, pour se
buter une nouvelle fois à un échec :
*[tres_intelligent(X)] {}
*[intelligent(X),sympathique(X)] {}
[debrouillard(X),humain(X),sympathique(X)] {}
[humain(chico),sympathique(chico)] {X=chico}
[femme(chico),sympathique(chico)] {X=chico}
ÉCHEC
Cette fois, l’interprète Prolog reculera au point de choix associé au but intelligent(X),
puisqu’il n’y a plus de choix possible avec le but humain. Continuant à partir de
ce point, il faudra maintenant résoudre le but dauphin(X). La variable X sera
instanciée par la constante flipper. Le résolution pourra alors continer et termi-
ner avec succès :
*[tres_intelligent(X)] {}
[intelligent(X),sympathique(X)] {}
[dauphin(X),sympathique(X)] {}
[sympathique(flipper)] {X=flipper}
[] {X=flipper}
Remarquez qu’il reste encore un point de choix qui n’a pas été considéré. Ainsi,
si on demande une nouvelle solution, l’interpréteur Prolog reculera à ce point de
choix. Dans ce cas, une nouvelle résolution à partir de ce point tentera de résoudre
le but professeur_ia(X), ce qui se terminera avec succès, avec X = michel :
[tres_intelligent(X)] {}
[professeur_ia(X)] {}
[] {X=michel}
3.4 Exercices
3.1 Traduire en Prolog les faits suivants :
— João aime Maria.
— Paulo aime Antônio.
— Antônio aime Claudia.
— Fernando aime Claudia.
— Claudia aime tous ceux qui l’aiment.
— Personne n’aime Paulo.
— Deux personnes de sexes différents qui s’aiment mutuellement sont aussi
des amants.
22
3.2 Supposons un édifice contenant un ensemble de salles. On utilise une relation
connexe(X,Y) pour indiquer que l’on peut passer directement de la salle X à la
salle Y, et vice versa. Supposant que la constante exterieur désigne l’extérieur
de l’édifice, définissez le prédicat sortir(X), dont la résolution réussit s’il existe
un chemin pour se rendre à l’extérieur à partir de la salle X.
Exemple :
connexe(salle1,salle2).
connexe(salle2,salle5).
connexe(salle3,exterieur).
connexe(salle4,salle7).
connexe(salle5,salle6).
connexe(salle5,salle8).
connexe(salle6,salle9).
connexe(salle9,exterieur).
?- sortir(salle1).
Yes
?- sortir(salle4).
No
4 Opérateurs arithmétiques
4.1 Opérateurs
Prolog permet les opérations logiques suivantes : X < Y, X > Y, X =<
Y, X >= Y. À noter que =< et >= signifient "plus petit ou égal" et "plus grand
ou égal", respectivement.
Pour utiliser ces opérateurs, les variables doivent être instanciées :
?- 4 >= 3.
Yes
?- X >= 3.
No
23
l’expression contient des variables, celles-ci doivent être instanciées avec une va-
leur numérique.
Exemples :
?- X is 3+4.
X = 7
?- 7 is 3 + X.
Error
?- X = 4, Y is 2 * X.
X = 4
Y = 8
?- X = 7, X is 3 + 4.
X = 7
Yes
?- X = 6, X is 3 + 4.
No
?- X = 5 + 2, X is 3 + 4.
No
Erroné:
convertir(0,0).
convertir(suc(X),Y+1):-
convertir(X,Y).
Correct:
24
convertir(0,0).
convertir(suc(X),Z):-
convertir(X,Y),
Z is Y+1.
Le premier programme est erroné parce qu’il n’évalue pas l’expression à chaque
pas de l’exécution. Il construit un structure en utilisant l’opérateur + :
?- convertir(suc(suc(0)),X).
X = 0 + 1 + 1
?- convertir(suc(suc(0)),X).
X = 2
Parce que l’opérateur is ne permet pas d’utiliser une variable libre (non instan-
ciée) à droite, la requête suivante retournera une erreur d’exécution :
?- convertir(X,2).
Error
4.2 Exercices
4.1 Definissez un programme puissance(X,N,P) qui unifie P avec la va-
leur X N .
?- puissance(2,3,X).
X = 8
5 Listes
5.1 Définitions
Une liste est une structure particulière qui représente une séquence d’objets,
qui est construite de manière récursive avec les deux éléments suivants :
1. la liste vide [ ]
2. le foncteur ”.”, qui ajoute un item en tête de liste
Exemple : la séquence 1, 2 et 3 est représentée ainsi :
25
Il existe une autre notation plus pratique : [1,2,3]
Une liste peut contenir des éléments de types différents :
Voici une autre notation qui distingue la tête du reste de la liste (aussi appelé queue
de la liste) :
Il est possible d’isoler plus d’un élément en tête de liste. Par exemple, le terme
suivant s’unifie avec n’importe quelle liste qui commence par les éléments 1 et 2 :
[1,2|X]
?- [1,2|X] = [1,2]
X = []
?- [1,2|X] = [1,2,7,3,4]
X = [7,3,4]
p([1,2,3]).
p([je,mange,[un,gateau,au,chocolat]]).
?- p([X|Y]).
X = 1
Y = [2,3] ;
26
X = je
Y = [mange,[un,gateau,au,chocolat]]
?- p([_,_,[_|Y]]).
Y = [gateau,au,chocolat]
Remarque : La queue d’une liste peut être la liste vide. Dans l’exemple qui
suit, X s’unifie avec le premier élément de la liste [4], qui est la constante 4. Y
doit être unifié avec le reste de la liste. Comme il n’y a aucun élément après 4, il
sera donc unifié avec la liste vide.
?- [X|Y] = [4].
X = 4
Y = []
?- pred([],[1]) = pred(X,[Y|X]).
X = []
Y = 1
La liste vide ne peut pas s’unifier avec une liste qui contient au moins un élément :
?- [X] = [].
No
?- [X] = [1,2]
No
?- p([X,Y],[X|Y]) = p(Z,Z).
Ici, Z doit s’unifier d’abord avec [X,Y]. Elle sera donc instanciée par cette ex-
pression. Ensuite, les deuxièmes arguments doivent être unifiés entre eux. Comme
Z a déjà été instanciée par le terme [X,Y], il faudra alors unifier celui-ci avec le
terme [X|Y]. Les deux têtes s’unifient sans problème, puisqu’il s’agit de la même
variable non instanciée. Il faudra alors unifier les deux queues, c’est-à-dire [Y]
et Y. Mais cela est impossible puisqu’il faudrait unifier une liste avec une autre
liste dont l’unique élément est la liste elle-même, résultant ainsi en une structure
infinie :
27
Y = [[[[...]]]]
Voici un autre exemple d’unification :
?- [X,a|Z] = [b,a,a|K]
X = b
Z = [a|K]
Ici, nous tentons d’unifier une liste qui contient au moins deux éléments avec
une autre liste qui en contient au moins trois. Les deux premiers éléments doivent
être unifiés : X = b et a = a. Il reste alors à unifier les deux restes, qui sont,
respectivement, Z et [a|K], ce qui se fait trivialement en instanciant la variable
Z.
5.3 Exercices
5.1 Quel est le résultat des tentatives d’unification suivantes :
p(X,[3|X]) = p(Z,[3])
p(X,[3|X]) = p([],[3,[]])
[roberta] = [X|Y]
[[a,b],X|Z] = [Y,Y]
pred([],[1]) = pred(X,[Y|X])
[curitiba,paranagua,pontagrossa|X] = [curitiba|Z]
[X,Y] = [X|Y]
[X,Y] = [X|Z]
[X,a|Z] = [b,a,a|K]
[[]|Y] = [[],[],[]]
[X,Y|Z] = [a,b,c,d]
[X|[Y|Z]] = [a,b,c,d]
[X|[Y|Z]] = [X,Y|Z]
[[X|Y]|Z] = [a,b,c,d]
[[X|Y]|Z] = [[a,b,c,d]]
[[X|Y]|Z] = [[a,b,c],[d,e],[f]]
6 Processus récursif
6.1 Definition
En Prolog, on utilise de manière systématique la récursivité pour résoudre un
problème. Un processus récursif consite en :
28
— un pas récursif qui accomplit une partie du travail (normalement sur un
morceau de la structure manipulée) et qui appelle récursivement la réso-
lution du même prédicat, mais avec le reste des données qui n’ont pas été
traitées ;
— une condition d’arrêt, qui termine la résolution.
factoriel(0,1).
factoriel(X,F):-
Y is X-1,
factoriel(Y,Fy),
F is Fy * X.
?- factoriel(4,X).
X = 24
?- factoriel(X,24).
Error
La seconde requête échoue parce que le programme n’a pas été écrit de manière
à accepter une variable libre comme premier argument. Ce qui se produit dans ce
cas est que la résolution du but Y is X-1, dans le corps de la seconde clause, ne
sera pas possible puisque la variable X doit être instanciée, comme l’exige l’opéra-
teur is.
member(X,[X|_]).
member(X,[_|Y]):-
member(X,Y).
?- member(4,[2,3,4,8]).
29
Yes
Il est intéressant de savoir qu’on peut utiliser le même prédicat pour extraire
séquentiellement les éléments de la liste. Lors de la résolution, le premier élément
de la liste est retourné. Comme il y a un point de choix, on peut obtenir les autres
éléments par une suite de retours arrière :
?- member(X,[2,3,4]).
X = 2 ;
X = 3 ;
X = 4 ;
No
La figure 1 montre les détails de la résolution de cette requête (les points de
choix sont indiqués en italique).
Il faut éviter ce type de définition. Il est en effet inutile d’utiliser des variables
différentes dans la tête de la première clause, puisque de toute façon on veut les
unifier.
30
Première solution retournée: Dernière tentative:
member(X,[2,3,4])
member(X,[2,3,4])
X = X1
X=2
member(X1,[3,4])
OK
X1 = X2
member(X,[2,3,4]) X2 = X3
X = X1 member(X3,[])
member(X1,[3,4])
X1 = 3 ÉCHEC
OK
member(X,[2,3,4])
X = X1
member(X1,[3,4])
X1 = X2
member(X2,[4])
X2 = 4
OK
31
Le problème du test réalisé dans le corps de la seconde clause est plus subtil.
Si la requête ne contient aucune variable libre, le résultat est le même. Par contre
si dans la requête le premier argument est une variable libre, le comportement est
bien différent. Dans ce cas, une seule réponse sera retournée :
?- member(X,[2,3,4]).
X = 2 ;
No
Ce qui se produit est que pour obtenir la seconde solution, il faut retourner au
point de choix qui nous amène a faire une tentative avec la seconde clause. Mais
pour résoudre avec cette clause, on devra éventuellement résoudre le but X \= 2.
Or ce but échouera, puisque la variable X peut être unifiée avec 2, et on demande
que ce ne soit pas le cas.
Pour bien saisir l’avantage de l’unification, on peut comparer avec ce que serait
une implémentation de member en C++. D’abord voici l’équivalent de l’implémen-
tation des listes :
template <typename T>
class ListElem
{
public:
friend class List<T>;
ListElem(T h, ListElem<T>* t) : = head_(h), tail_(t) {}
private:
T head_;
ListElem<T>* tail_;
};
32
};
femme(ana).
femme(clara).
femme(maria).
liste_femme([]).
liste_femme([X|Xs]):-
femme(X),
write(X),
nl,
liste_femme(Xs).
liste_femme([_|Xs]):-
liste_femme(Xs).
?- liste_femme([ana,paulo,clara,roberto]).
ana
clara
Yes
33
On peut faire une version non récursive du même programme, en utilisant le
prédicat fail. Ce prédicat échoue toujours lorsqu’on essait de le résoudre, forçant
ainsi un retour arrière. Voici le programme.
liste_femme(L):-
member(X,L),
femme(X),
write(X),
nl,
fail.
liste_femme(_).
append([],X,X).
append([X|Xs],Y,[X|Zs]):-
append(Xs,Y,Zs).
34
?- append([a,b,c],[d,e],R).
R = [a,b,c,d,e]
?- append(X,[d,e],[a,b,c,d,e]).
X = [a,b,c]
?- append(X,Y,[a,b,c]).
X = []
Y = [a,b,c] ;
X = [a]
Y = [b,c] ;
X = [a,b]
Y = [c] ;
X = [a,b,c]
Y = [] ;
No
append(X,Y,[a,b,c])
X=[]
Y = [a,b,c]
Aussi étrange que cela puisse paraître, on peut même utiliser le prédicat append
avec des variables non instanciées pour les deux derniers arguments :
35
append(X,Y,[a,b,c]) append(X,Y,[a,b,c])
X=[a|Xs] X=[a|Xs]
Y = Y1 Y = Y1
append(Xs,Y1,[b,c]) append(Xs,Y1,[b,c])
Xs=[]
Y1 = [b,c]
append(X,Y,[a,b,c]) append(X,Y,[a,b,c])
X=[a|Xs] X=[a|Xs]
Y = Y1 Y = Y1
append(Xs,Y1,[b,c]) append(Xs,Y1,[b,c])
Xs=[b|Xs1] Xs=[b|X1s]
Y1 = Y2 Y1 = Y2
append(Xs1,Y2,[c]) append(X1s,Y2,[c])
X1s = []
Y2 = [c]
36
?- append([1,2],X,Y).
X = K
Y = [1,2|K]
6.4 Exercices
6.1 Soit les faits suivants :
suivant(a,b).
suivant(b,c).
suivant(c,d).
...
suivant(y,z).
suivant(z,a).
Écrivez un programme Prolog qui utilise ces faits pour encoder un nom en
suivant l’algorithme suivant. Soit n la position d’une lettre dans l’alphabet. Chaque
lettre du nom est substituée par la lettre qui est à la position n + x dans l’alphabet,
où x est spécifié dans l’appel du programme. Par exemple, si x = 3, la lettre d
sera substituée par la lettre g, et la lettre z par la lettre c. Le programme reçoit une
liste de lettres qui représente le nom et retourne une liste de lettres qui représente
le nom encodé :
?- encoder([m,i,c,h,e,l],3,X).
X = [p,l,f,k,h,o]
mystere(N,Res):-
magique(N,[],L),
reverse(L,L2),
append(L,[N|L2],Res).
magique(1,L,L).
magique(N,A,L):-
N2 is N-1,
magique(N2,[N2|A],L).
?- mystere(4,L).
37
b) Quel sera le résultat si on demande une seconde solution ?
c) Modifiez le programme de manière à ce qu’il ne puisse pas retourner plus d’une
solution.
mystere(X,Y,Z):-
myst1(X,Y,Z).
?- mystere([t,b,u,e,d,m,o],X,Y).
?- mystere(X,[1],Z),
6.4 On représente une matrice n × n par une liste de listes, où chaque liste corres-
pond à une rangée de la matrice. Soit par exemple la matrice suivante :
a b c
d e f
g h i
Voici la représentation de cette matrice :
[ [a, b, c],
[d, e, f],
[g, h, i] ]
38
La position d’un élément de la matrice est représentée par une paire (x, y),
où (0, 0) désigne le coin supérieur gauche et (n, n) le coin inférieur droit. Dans
l’exemple, les éléments aux positions (0, 0) et (3, 3) sont a e i, respectivement.
Définissez en Prolog le prédicat chercher(X,Y,Matrice,Elem), qui
instancie Elem avec l’élément qui se trouve à la position (X,Y) dans la matrice :
?- chercher(2,2,[[a,b,c],[d,e,f],[g,h,i]],Z).
Z = e
p(X,[],[X]).
p(X,[Y|L],[X,Y|L]):-
X < Y.
p(X,[Y|L],[Y|R]):-
p(X,L,R).
?- p(5,[2,3,9],Z).
Jorge
PÈRE
Pedro Maria
PÈRE MÈRE MÈRE
MÈRE PÈRE
Rui
Une manière de représenter cet arbre est une liste de termes formés par les
foncteurs pere et mere.
Définissez un prédicat descendant_commun qui reçoit trois arguments. Les
deux premiers sont les noms de deux personnes. Le troisième est un arbre généa-
logique qui est représenté par une liste qui contient toutes les relations de parenté,
en utilisant les foncteurs pere et mere. Par exemple :
39
?- descendant_commun(ana,leo,
[pere(jorge,maria), mere(maria,leo),
mere(maria,luis), pere(pedro,ana),
mere(ana,rui), pere(leo,rui)]).
Yes
?- descendant_commun(pedro,jorge,
[pere(jorge,maria), mere(maria,leo),
mere(maria,luis), pere(pedro,ana),
mere(ana,rui), pere(leo,rui)]).
Yes
?- descendant_commun(pedro,luis,
[pere(jorge,maria), mere(maria,leo),
mere(maria,luis), pere(pedro,ana),
mere(ana,rui), pere(leo,rui)]).
No
P
¬S ∧ Q
P ∨ ¬(T ∧ R)
Cette base serait représentée par la liste suivante :
40
Yes
?- verif([p, and(neg(s),q), or(p,neg(and(t,r)))],[p,s,q]).
No
plus(0,X,X).
plus(suc(X),Y,suc(Z)):-
plus(X,Y,Z).
?- plus(suc(suc(0)),suc(suc(0)), suc(suc(suc(suc(0))))).
Yes
?- plus(suc(suc(0)),suc(suc(0)), suc(suc(0))).
No
41
Dans une interprétation procédurale, il peut être utilisé pour calculer la somme
de deux nombres :
?- plus(suc(suc(0)),suc(suc(0)), X).
X = suc(suc(suc(suc(0))))
Étant donné qu’un clause en Prolog ne distingue pas les entrées des sorties, elle
peut avoir plusieurs interprétations différentes. Par exemple, le prédicat plus peut
ête utilisé pour réaliser un addition, tout comme une soustraction :
?- plus(suc(suc(0)),X , suc(suc(suc(suc(0))))).
X = suc(suc(0))
factoriel(0,1).
factoriel(X,F):-
Y is X-1,
factoriel(Y,Fy),
F is Fy * X.
Nous avons déjà utilisé ce programme avec une interprétation procédurale. Ap-
paremment, il permet aussi une interprétation déclarative, puisqu’il peut être utilisé
pour vérifier si 4! = 24 :
?- factoriel(4,24).
Yes
Mais cela est une illusion, puisqu’en fait, dans les cas où il devrait échouer, avec
la requête factoriel(3,24) par exemple, il entrera plutôt dans une boucle
infinie (pourquoi ?). Pour résoudre ce problème, il faut ajouter un autre test dans la
dernière clause :
factoriel(0,1).
factoriel(X,F):-
X > 0,
Y is X-1,
factoriel(Y,Fy),
F is Fy * X.
Remarquez que cette nouvelle version n’accepte pas toutes les possibilités de
requête. Par exemple, on ne peut pas l’utiliser pour identifier le nombre x tel que
x! = 24 :
42
?- factoriel(X,24).
proc_fact(X,Y,X):-
factoriel(X,Y).
proc_fact(X,Y,R):-
X2 is X+1,
X2 =< Y,
proc_fact(X2,Y,R).
Remarquons que la seconde clause utilise un prédicat intermédiaire proc_fact,
qui calcule itérativement le factoriel de tous les nombres à partir de 0, jusqu’à ce
que l’on tombe sur une valeur qui correspond à l’entrée Y ou qu’on dépasse cette
valeur (dans ce dernier cas, il aura échec).
Pour terminer cette discussion, voyons un dernier exemple qui permet lui aussi
les deux interprétations. Il s’agit du prédicat next(X,L,Y), qui réussit si Y suit
immédiatement X dans la liste L. Voici le programme :
next(X,[X,N|_],N).
next(X,[_|Reste],N):-
next(X,Reste,N).
On peut facilement vérifier que ce programme permet l’interprétation déclara-
tive pour déterminer si deux éléments sont voisins dans une liste :
43
?- next(2,[1,2,3,4],3).
Yes
?- next(2,[1,2,3,4],4).
No
Il permet aussi toutes les combinaisons possibles d’instances pour les variables
X et Y :
?- next(2,[1,2,3,4],X).
X = 3
?- next(X,[1,2,3,4],4).
X = 3
?- next(X,[1,2,3,4],Y).
X = 1
Y = 2 ;
X = 2
Y = 3 ;
X = 3
Y = 4 ;
No
44
qui sera retournée comme résultat. On peut éventuellement avoir des cas où un
item traité dans la structure d’entrée n’a aucun correspondant dans la structure de
sortie. Il se peut aussi que l’item ait plusieurs valeurs qui lui correspondent dans
la structure de sortie. L’important ici est qu’il doit y avoir un parallélisme entre les
éléments de la structure d’entrée et ceux de la structure de sortie.
Exemples :
Programme pour extraire d’une liste les nombres inférieurs à une valeur fixée :
filtrer([],_,[]).
filtrer([X|R],Max,[X|R2]):-
X < Max,
filtrer(R,Max,R2).
filtrer([X|R],Max,R2):-
X > Max,
filtrer(R,Max,R2).
?- filtrer([4,10,5,0,7,3], 5, L).
L = [4,5,0,3]
eliminer_rep([],[]).
eliminer_rep([X,X|R],L):-
eliminer_rep([X|R],L).
eliminer_rep([X|R],[X|R2]):-
eliminer_rep(R,R2).
?- eliminer_rep([1,2,2,2,3,6,7,7,10],L).
L = [1,2,3,6,7,10]
?- eliminer_rep([1,2,2,2,3,6,2,2,2,7,7,10],L).
L = [1,2,3,6,2,7,10]
eliminer_rep([],[]).
eliminer_rep([X|R],L):-
member(X,R),
eliminer_rep(R,L).
45
eliminer_rep([X|R],[X|L]):-
eliminer_rep(R,L).
Défini ainsi, le programme retirera toutes les répétitions, qu’elles soient consécu-
tives ou non :
?- eliminer_rep([1,2,2,2,3,6,7,7,10],L).
L = [1,2,3,6,7,10]
?- eliminer_rep([1,2,2,2,3,6,2,2,2,7,7,10],L).
L = [1,2,3,6,7,10]
const_exp(Liste,Exp):-
const(Liste,Exp,[]).
const([’(’|Liste],exp(A1,Op,A2),Reste):-
chercher_arg(Liste,A1,[Op|R1]),
chercher_arg(R1,A2,[’)’|Reste]).
chercher_arg([X|Reste],X,Reste):-
X \= ’(’.
chercher_arg([’(’|Reste],Exp,R2):-
const([’(’|Reste],Exp,R2).
?- const_exp([’(’,3,*, ’(’,4,-,a,’)’,’)’],Exp).
Exp = exp(3,*,exp(4,-,a))
46
inverser(X,L):-
inv(X,[],L).
inv([],L,L).
inv([X|R],L,L2):-
inv(R,[X|L],L2).
?- inverser([5,4,3,2],X).
X = [2,3,4,5]
inserer([],X,[X]).
inserer([X|R],Y,[Y,X|R]):-
Y < X.
inserer([X|R],Y,[X|R2]):-
Y > X,
inserer(R,Y,R2).
trier(L,Lord):-
tri_ins(L,[],Lord).
tri_ins([],L,L).
tri_ins([X|R],L,Lord):-
inserer(L,X,L2),
tri_ins(R,L2,Lord).
?- trier([3,7,5,9,10,2],X).
X = [2,3,5,7,9,10]
eval(X,_,X):-
number(X).
eval(X,Env,Val):-
atom(X),
member([X,Val],Env).
eval(exp(X,Op,Y),Env,Val):-
eval(X,Env,X1),
47
eval(Y,Env,Y1),
apply(Op,X1,Y1,Val).
?- eval(exp(3,*,exp(4,-,a)),[[a,2],[b,8]],X).
X = 6
Pour évaluer une expression, le programme reçoit deux structures. Une est
l’expression, qui peut contenir des constantes qui ne sont pas des nombres mais
qui représentent plutôt des variables (ne pas confondre avec des variables Prolog).
L’autre est une liste qui indique la valeur associée à chaque "variable" que contient
l’expression.
Remarque : Dans cet exemple, on utilise les prédicat pré-définis (voir la section
13, qui décrit ces prédicats) number(X) et atom(X).
Voici un programme pour extraire d’une liste les éléments qui suivent immé-
diatement un élément spécifié :
?- sousliste_apres(4,[1,2,3,4,5,3,2,1],L).
L= [5,3,2,1]
Le problème avec ce programme est que si un élément apparaît deux fois, nous
aimerions que la liste retournée soit celle qui suit la dernière occurrence de cet
élément :
?- sousliste_apres(3,[1,2,3,4,5,3,2,1],L).
L= [2,1]
48
sousliste_apres(X, [X|Reste], Reste):-
\+ member(X,Reste).
sousliste_apres(X,[_|Reste], R):-
sousliste_apres(X,Reste,R).
8.4 Exercices
8.1 Écrivez un programme pour dupliquer les éléments d’une liste :
?- dupliquer([a,c,f],X).
X = [a,a,c,c,f,f]
?- somme([3,7,9],X).
X = 19
8.3 Écrivez un programme qui reçoit une valeur et partitionne une liste, retournant
une liste qui contient toutes les items qui sont inférieurs ou égaux a cette valeur, et
une autre liste qui contient les nombres supérieurs à cette valeur :
?- partition([3,24,0,10,12],8,X,Y).
X = [3,0]
Y = [24,10,12]
8.4 Écrivez un programme qui détermine si une liste est une sous-liste d’une autre
liste :
?- sousliste([a,f],[w,z,a,f,u]).
Yes
?- sousliste([a,u],[w,z,a,f,u]).
No
8.5 Écrivez un programme qui détermine si tous les éléments d’une liste sont inclus
dans une autre liste :
49
?- inclus([a,u],[w,z,a,f,u,h]).
Yes
?- inclus([a,k],[w,z,a,f,u,h]).
No
8.6 Écrivez un programme qui traduit en français une liste de mots en anglais et
vice-versa :
?- traduire([i,eat,an,orange],P).
P = [je,mange,une,orange]
?- traduire(E,[je,suis,ici]).
E = [i,am,here]
*8.7 Modifiez le programme précédent pour traiter les cas où un mot correspond à
plus d’un mot dans l’autre langue :
?- traduire2([paul,will,go,to,montréal],F).
P = [paul,ira,à,montréal]
?- traduire2([paul,talked,to,robert],F).
P = [paul,a,parle,à,robert]
?- traduire2(E,[je,cherche,la,salle,de,bain]).
E = [i,am,looking,for,the, bathroom]
?- concatener([[a,b],[c,d,e],[f,g]],L).
L = [a,b,c,d,e,f,g]
?- concatener([[],[1,2]],L).
L = [1,2]
?- concatener([[],[]],L).
L = []
?- concatener([],L).
L = []
8.10 Écrivez une programme qui reçoit une liste et qui retourne une liste ne conte-
nant que les éléments qui se retrouvent plus d’une fois dans la liste fournie :
?- elem_repetes([a,b],L).
L = []
50
?- elem_repetes([a,a,b],L).
L = [a]
?- elem_repetes([a,b,c,c,d,e,c,d,d,k,l,e],L).
L = [c,d,e]
8.11 Écrivez un programme qui intercale les éléments de deux listes. Si elles sont
de tailles différentes, on complète avec les items restants dans la liste de plus grande
taille.
?- intercaler([],[], L).
L = []
?- intercaler([a],[], L).
L = [a]
?- intercaler([],[1,2], L).
L = [1,2]
?- intercaler([1,2],[], L).
L = [1,2]
?- intercaler([a,b],[1,2,3,4], L).
L = [a,1,b,2,3,4]
?- intercaler([a,b,c,d],[1,2,3,4], L).
L = [a,1,b,2,c,3,d,4]
8.12 Écrivez un programme qui aplatit une liste pouvant contenir un nombre indé-
fini de listes imbriquées. (essayez d’écrire une version efficace qui n’utilise pas le
prédicat append) :
?- aplatir([1,2,[3,4],5],L).
L = [1,2,3,4,5]
?- aplatir([1,2,[],[2,[6,7,[10]]]], L).
L = [1,2,2,6,7,10]
?- aplatir([[[]]], L).
L = []
8.13 Écrivez un programme qui reçoit une liste et qui retourne une liste de paires,
où chaque paire indique le nombre de fois qu’un élément apparaît dans la liste.
(indice : essayez d’utiliser un accumulateur et le prédicat num_occ de la section
9) :
?- num_app([a,b,a,d,c,c,a,d],L).
L = [[c,2],[d,2],[b,1],[a,3]]
51
8.14 Modifiez le programme de l’exercice 3.2 pour obtenir le chemin qui permet
de sortir d’une salle :
connexe(salle1,salle2).
connexe(salle2,salle4).
connexe(salle4,exterieur).
connexe(salle5,salle1).
connexe(salle5,exterieur).
connexe(salle6,salle3).
connexe(salle3,salle7).
connexe(salle7,salle6).
?- sortir(salle1,Chemin).
Chemin = [salle1, salle2, salle4]
?- sortir(salle3,Chemin).
No
8.16 Soit un programme qui retourne la valeur maximale d’une liste de nombres.
a) Écrivez une version récursive de ce programme qui n’utilise pas la technique
de l’accumulateur.
b) Écrivez une version qui utilise la technique de l’accumulateur.
member(X,[X|_]).
member(X,[_|Y]):-
member(X,Y).
?- member(Z,[a,b,c]).
Z = a ;
52
Z = b ;
Z = c ;
No
Si on inverse l’ordre des clause, l’ordre des solutions retournées est aussi in-
versé :
member(X,[_|Y]):-
member(X,Y).
member(X,[X|_]).
?- member(Z,[a,b,c]).
Z = c ;
Z = b ;
Z = a ;
No
Dans d’autres cas, l’ordre peut faire en sorte que le processus récursif ne s’arrête
jamais :
append([X|Xs],Y,[X|Zs]):-
append(Xs,Y,Zs).
append([],X,X).
?- append(X,[1,2],Y).
... (boucle infinie)
Finalement, si l’ordre est incorrect, on peut obtenir des réponses erronées. Sup-
posons que nous voulons écrire un programme qui calcule le nombre de fois qu’un
élément apparaît dans une liste. Le programme correct est le suivant :
num_occ(_,[],0).
num_occ(X,[X|Reste],Num):-
num_occ(X,Reste,N),
Num is N + 1.
num_occ(X,[_|Reste],Num):-
num_occ(X,Reste,Num).
?- num_occ(b,[a,b,f,b,f,k,b,a],X).
X = 3
53
La première classe traite le cas de la liste vide. La seconde traite le cas où l’élé-
ment recherché est le premier de la liste. Dans ce cas, on résout récursivement le
prédicat avec le reste de la liste et on retourne le résultat obtenu + 1. Finalement, si
on n’a affaire à aucun de ces cas, on résout récursivement le prédicat et on retourne
la valeur obtenue.
Voyons maintenant le même programme, mais avec les deux dernières clauses
inversées :
num_occ(_,[],0).
num_occ(X,[_|Reste],Num):-
num_occ(X,Reste,Num).
num_occ(X,[X|Reste],Num):-
num_occ(X,Reste,N),
Num is N + 1.
?- num_occ(b,[a,b,f,b,f,k,b,a],X).
X = 0
?- num_occ(b,[a,b,f,b],X).
X = 2 ;
X = 1 ;
X = 1 ;
X = 0 ;
No
54
Essayons de comprendre pourquoi on obtient ces réponses erronées. D’abord,
voici la situation lorsque la première solution est retournée (les points de choix sont
toujours indiqués en italique) :
num_occ(b,[a,b,f,b],X) ===> X = 2
X = Num
num_occ(b,[b,f,b],Num)
Num = Num1
N = Num2
num_occ(b,[b],Num2)
Num2 = Num3
N1 = 0
num_occ(b,[],0)
55
num_occ(b,[a,b,f,b],X) ===> X = 1
X = Num
num_occ(b,[b,f,b],Num)
Num = Num1
N = Num2
num_occ(b,[b],Num2)
Num2 = Num3
num_occ(b,[],Num3)
Num3 = 0
num_occ(b,[],0)
Pour obtenir la troisième solution, on recule au dernier point de choix, soit le but
num_occ(b,[b,f,b],Num). En poursuivant la résolution à partir de ce point,
en utilisant cette fois-ci la dernière clause, on obtient la solution, avec en prime un
nouveau point de choix :
56
num_occ(b,[a,b,f,b],X) ===> X = 1
X = Num
num_occ(b,[b,f,b],Num)
Num = Num1
num_occ(b,[f,b],Num1)
Num1 = Num2
num_occ(b,[b],Num2)
Num2 = Num3
num_occ(b,[],N) Num3 is N + 1
N = 0
num_occ(b,[],0)
num_occ(b,[a,b,f,b],X) ===> X = 0
X = Num
num_occ(b,[b,f,b],Num)
Num = Num1
num_occ(b,[f,b],Num1)
Num1 = Num2
num_occ(b,[b],Num2)
Num2 = Num3
num_occ(b,[],Num3)
Num3 = 0
num_occ(b,[],0)
57
Pour éviter ces réponses additionnelles erronées, il faut mettre une condition sup-
plémentaire dans la dernière clause :
num_occ(_,[],0).
num_occ(X,[X|Reste],Num):-
num_occ(X,Reste,N),
Num is N + 1.
num_occ(X,[Y|Reste],Num):-
X \= Y,
num_occ(X,Reste,Num).
?- num_occ(b,[a,b,f,b],Z).
Z = 2 ;
No
Un autre exemple qui montre bien l’importance de l’ordre des clauses est la
seconde version du programme eliminer_rep, que nous avons vu à la section
8.1. Rappelons-nous que ce programme élimine les répétitions dans un liste :
eliminer_rep([],[]).
eliminer_rep([X|R],L):-
member(X,R),
eliminer_rep(R,L).
eliminer_rep([X|R],[X|L]):-
eliminer_rep(R,L).
eliminer_rep([],[]).
eliminer_rep([X|R],L):-
eliminer_rep(R,L),
member(X,L).
eliminer_rep([X|R],[X|L]):-
eliminer_rep(R,L).
?- eliminer_rep([1,2,3,2],Z).
Z = [1,3,2]
58
Voici l’état de la résolution la première fois que l’interpréteur Prolog rencontre un
échec :
eliminer_rep([1,2,3,2],Z)
Z = L
eliminer_rep([2,3,2],L) member(1,L)
L = L1
eliminer_rep([3,2],L1) member(2,L1)
L1 = L2
eliminer_rep([2],L2) member(3,L2)
L2 = L3
eliminer_rep([],L3) member(2,L3)
L3 = []
ECHEC
eliminer_rep([],[])
59
eliminer_rep([1,2,3,2],Z)
Z = L
eliminer_rep([2,3,2],L) member(1,L)
L = L1
eliminer_rep([3,2],L1) member(2,L1)
L1 = L2
eliminer_rep([2],L2) member(3,L2)
L2 = [2|L3]
eliminer_rep([],L3)
ECHEC
L3 = []
eliminer_rep([],[])
Z = L
eliminer_rep([2,3,2],L) member(1,L)
L = L1
eliminer_rep([3,2],L1) member(2,L1)
L1 = [3|L2]
eliminer_rep([2],L2)
L2 = L3
eliminer_rep([],L3) member(2,L3)
L3 = []
ECHEC
eliminer_rep([],[])
60
Remarquez qu’un nouveau point de choix est apparu avec le but eliminer_rep([2],L2).
C’est à ce point de choix qu’on poursuivra la résolution, avec encore une fois un
échec, mais plus haut dans l’arbre de résolution :
eliminer_rep([1,2,3,2],Z)
Z = L
eliminer_rep([2,3,2],L) member(1,L)
L = L1
eliminer_rep([3,2],L1) member(2,L1)
ECHEC
L1 = [3|L2]
eliminer_rep([2],L2) member(2,[3,2])
L2 = [2|L3]
eliminer_rep([],L3)
L3 = []
eliminer_rep([],[])
eliminer_rep([],[]).
eliminer_rep([X|R],L):-
member(X,R),
eliminer_rep(R,L).
eliminer_rep([X|R],[X|L]):-
eliminer_rep(R,L).
61
eliminer_rep([1,2,3,2],Z)
Z = L
member(1,[2,3,2]) eliminer_rep([2,3,2],L)
member(1,[3,2])
member(1,[2])
member(1,[])
ECHEC
62
eliminer_rep([1,2,3,2],Z)
Z = [1|L]
eliminer_rep([2,3,2],L)
L = L1
eliminer_rep([3,2],L1)
member(2,[3,2])
L1 = L2
OK
member(3,[2]) eliminer_rep([2],L2)
ECHEC
63
eliminer_rep([1,2,3,2],Z)
Z = [1|L]
eliminer_rep([2,3,2],L)
L = L1
eliminer_rep([3,2],L1)
member(2,[3,2])
L1 = [3|L2]
eliminer_rep([2],L2)
OK
L2 = L3
member(2,[]) eliminer_rep([],L3)
ECHEC
64
eliminer_rep([1,2,3,2],Z) ===> Z = [1,3,2]
Z = [1|L]
eliminer_rep([2,3,2],L)
L = L1
eliminer_rep([3,2],L1)
member(2,[3,2])
L1 = [3|L2]
eliminer_rep([2],L2)
OK
L2 = [2|L3]
eliminer_rep([],L3)
L = []
eliminer_rep([],[])
Remarquez qu’il reste un point de choix. Par conséquent, si nous demandons une
nouvelle solution, l’interpréteur reculera à ce point de choix et retournera une nou-
velle solution (erronée).
prix(pomme, 0.50).
prix(riz, 1.40).
prix(pain, 2.70).
65
cout_total([],0).
cout_total([X|Xs],N):-
cout_total(Xs,N2),
prix(X,P),
N is N2 + P.
?- cout_total([pomme,pain],X).
X = 3.2
Si nous voulons ajouter le lait dans la liste des produits qui ont un prix, nous
exécuterions la requête suivante :
assert(prix(lait,1.10)).
Maintenant, nous pouvons faire une requête qui aurait échoué auparavant :
?- cout_total([pomme,pain,lait],X).
X = 4.3
Pour modifier le prix d’un produit, il est nécessaire de retirer une clause et d’en
ajouter un nouvelle qui indique le nouveau prix :
?- retract(prix(pomme,_)), assert(prix(pomme,0.60)).
Yes
Jusqu’à maintenant, tous les exemples concernent des faits. Mais il est possible de
manipuler aussi des clauses. Voyez par exemple comment nous pourrions modifier
le programme pour que la prix des oranges soit toujours identique au prix des
pommes :
?- assert(prix(orange,P):- prix(pomme,P)).
Yes
:- dynamic prix/2.
66
11 Recupération de solutions multiples
11.1 Utilisation du retour arrière
Pour obtenir toutes les solutions d’une résolution, la seule manière que nous
connaissons jusqu’à maintenant est l’utilisation répétitive du point-virgule après
chaque solution retournée. Soit le programme suivant :
homme(roberto).
homme(paulo).
homme(joao).
homme(armando).
femme(lucia).
femme(clara).
pere(pauo,roberto).
pere(paulo,joao).
pere(armando,lucia).
pere(armando,clara).
Pour identifier tous les enfants de Paulo, nous utiliserions la méthode suivante :
?- pere(paulo,X).
X = roberto ;
X = joao ;
No
Mais cela ne fonctionne pas si nous désirons récupérer toutes les solutions pour
en faire un traitement. Par exemple, si nous voulons calculer le nombre d’enfants
que Paulo a, il nous faut une manière de mettre tous les enfants dans une liste et
calculer le nombre d’items que cette liste contient. On pourrait toujours résoudre
le problème en utilisant des prédicats dynamiques :
tous_les_enfants(X,L):-
assert_enfants(X),
retract_enfants(L).
assert_enfants(X):-
pere(X,F),
assert(enfant(F)),
fail.
assert_enfants(_).
67
retract_enfants([F|Fs]):-
retract(enfant(F)),
retract_enfants(Fs).
retract_enfants([]):-
\+ enfant(_).
Ce programme résout le problème en deux étapes. Dans une première étape, il
ajoute un fait pour chaque enfant de X. Remarquez l’usage de fail, pour forcer le
retour arrière, afin d’identifer tous les enfants de X (le prédicat assert n’a jamais
de point de choix). À la seconde étape, il retire tous les faits qui ont été ajoutés, et
les met dans une liste.
?- nombre_enfants(paulo,N).
N = 2
?- nombre_enfants(joao,N).
N = 0
68
Remarque : le prédicat length retourne 0 pour une liste vide.
Il n’est pas nécessaire que le premier argument soit une variable. Il peut être une
structure plus complexe. Supposons par exemple que nous voulions savoir qui sont
toutes les personnes impliquées dans une relation père-enfant. Dans ce cas, on peut
utiliser findall, en utilisant une paire de variables comme premier argument :
?- findall([X,Y],pere(X,Y),L).
L = [[paulo,roberto],[paulo,joao],
[armando,lucia],[armando,clara]]
Le second argument peut être une conjonction de plusieurs buts. Par exemple,
si nous désirons la liste de toutes les personnes qui ont une fille, nous exécuterions
la requête suivante :
?- findall(X,(pere(X,F),femme(F)),L).
L = [armando,armando]
La liste obtenue contient deux occurrences de Armando parce qu’il y a deux so-
lutions possibles qui impliquent Armando (il a deux filles). Pour éviter cette redon-
dance, on peut substituer la conjonction de buts par un prédicat a_une_fille
défini de la manière suivante :
a_une_fille(Personne):-
findall(X,pere(Personne,X),Enfants),
findall(X,(member(X,Enfants),femme(X)),Filles),
Filles \= [].
?- a_une_fille(paulo)
No
?- a_une_fille(armando)
Yes
?- a_une_fille(X)
X = _G109
69
Nous n’obtenons pas le résultat espéré (X = armando) à cause de la séman-
tique du prédicat findall. Par définition, les variables contenues dans les deux
premiers arguments du prédicat demeurent libres au retour de la résolution. Pour
solutionner notre problème, nous pouvons modifier le programme, en ajoutant un
prédicat qui vérifie si X est un hommme :
a_une_fille(Personne):-
homme(Personne),
findall(X,pere(Personne,X),Fils),
findall(X,(member(X,Fils),femme(X)),Filles),
Filles \= [].
?- a_une_fille(X)
X = armando ;
No
Maintenant, nous pouvons obtenir la liste non redondante des hommes qui ont une
fille :
?- findall(X,a_une_fille(X),L).
L = [armando]
70
p(1).
p(2).
p(3).
q(1,a).
q(1,b).
q(2,c).
q(2,d).
?- bagof(Y,(p(X),q(X,Y)),L).
Y = _G173
X = 1
L = [a, b] ;
Y = _G173
X = 2
L = [c, d] ;
No
a_une_fille(Personne):-
findall(X,pere(Personne,X),Enfants),
findall(X,(member(X,Enfants),femme(X)),Filles),
Filles \= [].
Il est important de noter, ici, que nous voulons un échec si la personne n’a pas
d’enfant, ou si elle a des enfants mais qu’aucun n’est une fille. Ceci suggère une
meilleure solution qui utlise le prédicat bagof :
a_une_fille3(Personne):-
bagof(X,(pere(Personne,X),femme(X)),_).
Remarquez que ce prédicat échoue si la personne n’a pas de fille. Si elle a une
fille, la résolution se termine avec succès, et on ne s’interesse pas à la liste retournée
par le prédicat bagof.
71
11.4 Exercices
11.1 Nous voulons utiliser le prédicat sortir de l’exercice 3.2 pour écrire un
programme qui retourne la liste de toutes les salles connectées avec l’extérieur.
Voici un programme qui ne fonctionne pas :
salles_connectees_avec_exterieur(Salles):-
findall(S,sortir(S),Salles).
11.2 En utilisant le prédicat findall, écrivez un programme qui reçoit deux listes
et retourne une liste de tous les éléments qui apparaissent dans les deux listes :
?- dans_les_deux_listes([a,b,f,g,z],[c,b,k,l,g,r],L).
L = [b,g]
?- dans_les_deux_listes([a,z],[c,b,k,l,g,r],L).
L = []
mystere(L1,L2,Res):-
findall(X*Y,(member(X,L1),member(Y,L2)),L3),
traiter(L3,Res).
traiter([],0).
traiter([X|Reste],N):-
traiter(Reste,N1),
N is X+N1.
12 Mécanismes de contrôle
12.1 Coupe-choix :
Le coupe-choix (cut) est le prédicat " !", qui est utilisé pour couper l’arbre de
recherche quand un retour arrière est effectué. Principalement, on l’utilise pour
empêcher qu’un programme ne tente de retourner une nouvelle solution. L’effet du
coupe-choix est le suivant :
72
Quand l’interpréteur Prolog rencontre un coupe-choix, il oublie
tous les points de choix qui ont été ajoutés lors de l’unification de la
tête de la clause et le but résolu, ainsi que lors de la résolution de tous
les buts qui se trouvent avant le coupe-choix dans le corps de la clause.
Pour mieux comprendre le principe du coupe-choix, voyons quelques exemples.
Voici un programme qui calcule la somme de tous les nombres de 1 à n :
somme_jusque(1,1).
somme_jusque(N,S):-
M is N-1,
somme_jusque(M,P),
S is P + N.
Le problème avec ce programme est qu’il entrera dans une boucle infinie si on
effectue un retour arrière :
?- somme_jusque(3,X).
X = 6 ;
boucle infinie...
somme_jusque(1,1):-!.
somme_jusque(N,S):-
M is N-1,
somme_jusque(M,P),
S is P + N.
pere(roberto,ana).
pere(paulo,romeu).
73
pere(michel,tomas).
pere(roberto,francisco).
age(roberto,23).
age(michel,35).
age(paulo,53).
age(tomas,7).
peres_jeunes([],[]).
peres_jeunes([X|Reste],[X|R]):-
pere(X,_),
age(X,I),
I < 36,
peres_jeunes(Reste,R).
peres_jeunes([_|Reste],R):-
peres_jeunes(Reste,R).
Le problème avec ce programme est qu’il peut retourner plus d’une solution :
?- peres_jeunes([roberto,paulo,michel],X).
X = [roberto, michel] ;
X = [roberto] ;
X = [roberto, michel] ;
X = [roberto] ;
X = [michel] ;
X = [] ;
No
Le premier point de choix qui sera considéré, lors du retour arrière, est le but
parents_jeunes([michel],R), qui pourra être réunifié avec la seconde
clause. La poursuite de la résolution à partir de ce point amènera l’ajout du but
peres_jeunes([],R) dans la résolvante, sans ajouter la constante michel
dans la liste de résultats.
Au second retour arrière, on recule au but pere(roberto,_), qui réussira à
nouveau, en unifiant avec le fait pere(roberto,francisco). On obtiendra
à nouveau la première solution, avec encore une fois un point de choix au but
peres_jeunes([michel],R).
74
peres_jeunes([roberto,paulo,michel],X)
X = [roberto | R]
R = R1
pere(roberto,ana)
peres_jeunes([michel],R1)
R=[michel | R2]
R2=[ ]
peres_jeunes([ ],[ ])
75
Finalement, il restera le point de choix associé au but initial, qui peut être unifié
avec la seconde clause. En redémarrant à partir de ce point, on obtient une nouvelle
solution : [michel]. Pour arriver à cette solution, un nouveau point de choix a
été ajouté, comme l’illustre la figure 6.
peres_jeunes([roberto,paulo,michel],X)
peres_jeunes([paulo,michel],X)
peres_jeunes([michel],R)
R=[michel | R2]
R2=[ ]
peres_jeunes([ ],[ ])
peres_jeunes([],[]).
peres_jeunes([X|Reste],[X|R]):-
pere(X,_),
76
age(X,I),
I < 36,!,
peres_jeunes(Reste,R).
peres_jeunes([_|Reste],R):-
peres_jeunes(Reste,R).
Il faut bien comprendre que dans cet exemple, il y a deux endroits dans le pro-
gramme qui sont des points de choix : la seconde clause et le but pere(X,_) dans
la seconde clause. Le coupe-choix qui a été ajouté résout une partie du problème.
Mais le programme est encore inefficace. Si on a un père qui a plus d’un fils et dont
l’âge est supérieur à 36 ans, on résoudra plusieurs fois le but pere(X,_) avec ce
père, pour échouer à chaque fois lors du test I < 36. On ne peut pas résoudre
ce problème en mettant le coupe-choix immédiatement après le but pere(X,_)
(pourquoi ?). Pour résoudre le problème, il faut plutôt définir un nouveau prédi-
cat est_un_pere et mettre le coupe-choix dans la définition de ce prédicat. Le
nouveau programme aurait alors la forme suivante :
peres_jeunes([],[]).
peres_jeunes([X|Reste],[X|R]):-
est_un_pere(X),
age(X,I),
I < 36,!,
peres_jeunes(Reste,R).
peres_jeunes([_|Reste],R):-
peres_jeunes(Reste,R).
est_un_pere(X):-
pere(X,_),!.
pere_jeune(X):-
pere(X,_),
age(X,I),
I < 36.
peres_jeunes([],[]).
peres_jeunes([X|Reste],[X|R]):-
77
pere_jeune(X),
peres_jeunes(Reste,R).
peres_jeunes([X|Reste],R):-
\+ pere_jeune(X),
peres_jeunes(Reste,R).
Malheureusement, cette solution n’est pas optimale. Nous avons éliminés les
réponses erronées, mais pas les réponses redondantes, qui sont dues au fait qu’un
père peut avoir plusieurs enfants. En d’autres mots, on a toujours un point de choix,
au but pere(X,_), qui se trouve dans la définition du prédicat pere_jeune.
Pour éliminer ce point de choix, nous pourrions redéfinir le prédicat pere_jeune,
en utilisant findall pour extraire tous les fils et en vérifiant que la liste résultante
ne soit pas vide. Cela fonctionne parce que le prédicat findall n’ajoute jamais
de point de choix :
pere_jeune(X):-
findall(F,pere(X,F),Fils),
Fils \= [],
age(X,I),
I < 36.
peres_jeunes([],[]).
peres_jeunes([X|Reste],[X|R]):-
pere_jeune(X),
peres_jeunes(Reste,R).
peres_jeunes([X|Reste],R):-
\+ pere_jeune(X),
peres_jeunes(Reste,R).
78
Il est très important de comprendre que l’effet du coupe-choix est d’éliminer
tous les points de choix qui ont été ajoutées depuis le moment où la tête de la clause
a été unifiée. Pour mieux comprendre, considérons le programme suivant :
p(1).
p(2).
d(2).
f(1).
c(2).
b(1).
b(2).
r(X):- p(X).
r(X):- f(X).
q(X):- r(X),!,c(X).
q(X):- b(X).
pred(X):- q(X),f(X).
pred(X):- d(X).
?- pred(1).
No
79
pred(1)
q(1) f(1)
r(1) ! c(1)
p(1)
OK
p(1).
p(2).
d(2).
f(1).
c(2).
b(1).
b(2).
r(X):- p(X).
r(X):- f(X).
q(X):- r(X),c(X).
q(X):- b(X).
pred(X):- q(X),!,f(X).
pred(X):- d(X).
Soit la requête suivante :
?- pred(2).
La figure 8 montre la situation lorsqu’on arrive au coupe-choix. Dans ce cas,
tous les points de choix seront retirés. Quand le but suivant, soit f(2), sera traité,
on se butera à un échec, sans aucune possibilité de retour arrière.
80
pred(2)
q(2) ! f(2)
r(2) c(2)
p(2) OK
OK
min(X,Y,X):-
X =< Y, !.
min(X,Y,Y).
?- min(2,3,X).
X = 2 ;
No
81
?- min(5,2,X).
X = 2
Ce type de coupe-choix est un "coupe-choix rouge", c’est-à-dire un coupe-
choix dangereux qu’il vaudrait mieux éviter. Le problème est que le programmeur
n’a pensé qu’à une utilisation où le dernier argument est toujours une variable libre.
Il n’a pas pensé à la possibilité d’utiliser de prédicat pour vérifier qu’un nombre
donné est le plus petit de deux nombres :
?- min(2,5,5).
Yes
On voit que dans ce cas, la résolution retourne avec succès, alors qu’elle aurait
du échouer. Pour éviter cela, nous pourrions ajouter un test :
min(X,Y,Z):-
X =< Y,
!,
Z=X.
min(X,Y,Y).
Mais cette solution rend le programme moins compréhensible. Il est important
de remarquer que ce problème ne se serait pas produit si le programmeur avait
utilisé une version plus intuitive, sans coupe-choix :
min(X,Y,X):-
X =< Y.
min(X,Y,Y):-
X > Y.
Remarquez qu’on peut toujours utiliser un coupe-choix pour rendre le pro-
gramme plus efficace, sans en changer la sémantique :
min(X,Y,X):-
X =< Y,!.
min(X,Y,Y):-
X > Y.
Ici, le coupe-choix ne fait qu’éviter une réunification avec la tête de la seconde
clause qui, de toute façon, entraînerait un échec lors de la résolution du but qui se
trouve dans le corps de cette clause.
Conclusion : Quand on introduit un coupe-choix dans la définition d’un pré-
dicat, il est très important de considérer l’effet de celui-ci dans les différentes ma-
nières d’appeler ce prédicat.
82
12.3 Négation :
En Prolog, il n’y a pas à proprement parler de négation. Il n’y a que l’opérateur
\+ qui représente la négation par échec. Ceci signifie que la résolution de
\+ But réussira seulement s’il n’existe aucune manière de résoudre But. Le but
ne peut pas être une variable libre :
?- \+ X .
Error
?- \+ member(3,[2,3,4])).
No
?- \+ member(3,[4,5,6])).
Yes
Il est très facile de définir la négation par échec en Prolog, en utilisant le prédi-
cat prédéfini fail :
neg_echec(X):- X,!,fail.
neg_echec(X).
Pour se convaincre que la négation par échec implémentée en Prolog n’est pas
équivalente à la négation en logique, il suffit de considérer le programme suivant :
etudiant_ancien(X):-
\+ nouveau(X),
etudiant(X).
nouveau(paulo).
etudiant(jorge).
12.4 Exercices
12.1 En utilisant le coupe-choix, modifiez le programme de l’exercice 8.14 pour
éliminer les solutions multiples.
83
m(a).
m(b).
m(k).
p(a,b).
p(a,c).
p(b,d).
p(b,e).
p(k,e).
q(c).
q(d).
q(e).
pred1(X,Y):-
m(X),
pred2(X,Y).
pred2(X,Y):-
p(X,Y),
!,
q(Y).
Quel sera le résultat de la requête suivante :
?- pred1(X,Z).
m(a).
m(c).
q(a).
r(a).
r(b).
r(c).
p(X):-
q(X),
!,
fail.
84
p(X):-
r(X).
s(X):-
m(X),
p(X).
?- s(X).
12.4 Pourquoi l’ajout d’un coupe-choix après le but pere(X,_) ne résout pas le
problème d’efficacité dans la définition de peres_jeunes ?
?- var(X).
Yes
?- var(23).
No
?- X=Y, Y=23, var(X).
No
nonvar(+Terme)
85
plus(X,Y,Z):-
nonvar(X),
nonvar(Y),
Z is X+Y.
plus(X,Y,Z):-
nonvar(X),
nonvar(Z),
Y is Z-X.
plus(X,Y,Z):-
nonvar(Y),
nonvar(Z),
X is Z-Y.
?- plus(3,4,R).
R = 7
?- plus(R,5,11).
R = 6
?- plus(X,Y,11).
No
integer(+Terme)
float(+Terme)
number(+Terme)
atom(+Terme)
?- atom(23).
No
?- atom(rodrigo).
Yes
86
?- atom(’UFPR’).
Yes
?- atom(X).
No
?- atom(suc(suc(0))).
No
?- atom("Estou chegando").
No
atomic(+Terme)
Réussit si Terme est une constante, qui peut éventuellement être un nombre.
?- atomic(23).
Yes
?- atomic(rodrigo).
Yes
?- atomic(’UFPR’).
Yes
?- atomic(X).
No
?- atomic(suc(suc(0))).
No
+Terme1 \= +Terme2
87
— Fautes de frappes, dans les noms de prédicat, par exemple. Le programme
compile, mais ne s’exécute pas correctement. Soit l’exemple suivant :
nombre_elme([],0).
nombre_elem([X|Reste],Z):-
nombre_elem(Reste,N),
Z is N+1.
La résolution échouera toujours, puique le prédicat nombre_elem n’a
pas de clause pour la liste vide.
pred(X,Y):-
mystere1(X,Z),
mystere2(Z,Y).
mystere1([],[]).
mystere1([X|Reste],Z):-
X < 0,
mystere1(Reste,Z).
mystere1([X|Reste],[X|Z]):-
mystere1(Reste,Z).
mystere2([],[]).
mystere2([X|Reste],[X|ResteFiltre]):-
filtre(X),
mystere2(Reste,ResteFiltre).
filtre(X):-
W is X mod 2,
W = 0.
?- pred1([5,4,-3],X).
No
88
Pour déboguer le programme, on peut demander une trace de la résolution,
en appelant auparavant le prédicat prédéfini trace. L’interpréteur exécutera alors
la résolution pas à pas. À chaque pas, il montre le prochain but à résoudre, de la
manière suivante :
Call: (niveau de profondeur dans l’arbre de résolution)
But
Il attend alors une action de notre part. Si on appuie sur la touche <enter>, il tente
l’unification, met à jour la résolvante, et montre le prochain but à résoudre.
Quand l’interpréteur sort de la résolution d’un but, il y a trois cas possibles.
L’échec, indiqué par la forme suivante :
Fail:(niveau de profondeur) But
Le succès, indiqué par la forme suivante :
Exit:(niveau de profondeur) But avec variables instanciées.
S’il y a échec, l’interprète recule au dernier point de choix, et affiche la ligne
suivante :
Redo:(niveau de profondeur) But qui correspond au dernier
point de choix .
Si on appuie à nouveau sur la touche <enter>, il tentera de redémarrer la ré-
solution à partir de ce point de choix. Voici la trace d’exéxution du programme
ci-dessus :
?- trace.
Yes
?- pred([5,4,-3],X).
Call: ( 8) pred1([5, 4, -3], _G200) ?
Call: ( 9) mystere1([5, 4, -3], _L168) ?
Call: ( 10) 5<0 ?
Fail: ( 10) 5<0 ?
Redo: ( 9) mystere1([5, 4, -3], _L168) ?
Call: ( 10) mystere1([4, -3], _G304) ?
Call: ( 11) 4<0 ?
Fail: ( 11) 4<0 ?
Redo: ( 10) mystere1([4, -3], _G304) ?
Call: ( 11) mystere1([-3], _G307) ?
Call: ( 12) -3<0 ?
Exit: ( 12) -3<0 ?
Call: ( 12) mystere1([], _G307) ?
Exit: ( 12) mystere1([], []) ?
Exit: ( 11) mystere1([-3], []) ?
Exit: ( 10) mystere1([4, -3], [4]) ?
Exit: ( 9) mystere1([5, 4, -3], [5, 4]) ?
Call: ( 9) mystere2([5, 4], _G200) ?
89
Call: ( 10) filtre(5) ?
^ Call: ( 11) _L234 is 5 mod 2 ?
^ Exit: ( 11) 1 is 5 mod 2 ?
Call: ( 11) 1=0 ?
Fail: ( 11) 1=0 ?
Fail: ( 10) filtre(5) ?
Fail: ( 9) mystere2([5, 4], _G200) ?
Redo: ( 11) mystere1([-3], _G307) ?
Call: ( 12) mystere1([], _G310) ?
Exit: ( 12) mystere1([], []) ?
Exit: ( 11) mystere1([-3], [-3]) ?
Exit: ( 10) mystere1([4, -3], [4, -3]) ?
Exit: ( 9) mystere1([5, 4, -3], [5, 4, -3]) ?
Call: ( 9) mystere2([5, 4, -3], _G200) ?
Call: ( 10) filtre(5) ?
^ Call: ( 11) _L193 is 5 mod 2 ?
^ Exit: ( 11) 1 is 5 mod 2 ?
Call: ( 11) 1=0 ?
Fail: ( 11) 1=0 ?
Fail: ( 10) filtre(5) ?
Fail: ( 9) mystere2([5, 4, -3], _G200) ?
Fail: ( 8) pred1([5, 4, -3], _G200)
Avec cet exemple, on peut voir que la résolution du but mystere1 réussit,
après deux retours arrière. Le premier but que l’on tente de résoudre est mystere2,
sans succès. L’interprète recule alors au dernier point de choix, qui est le but
mystere1([-3], _G307). Ce but peut s’unifier avec la tête de la dernière
clause du prédicat mystere1, retournant plus d’un résultat, mais la nouvelle
tentative de résolution de mystere2 échoue. Donc, la résolution de la requête
échoue, puisqu’il n’y a plus de point de choix.
Il est possible d’obtenir une trace plus courte. En effet, pour chaque but, on
peut demander qu’il soit résolu sans fournir les détails de la résolution, en entrant
la commande s (skip), au lieu d’appuyer sur la touche <enter>. Voici une trace du
même programme, mais sans les détails des première tentatives de résolution des
buts mystere1([5,4,-3],_L168]) et mystere2([5, 4], _G200) :
?- pred1([5,4,-3],X).
Call: ( 8) pred1([5, 4, -3], _G200) ?
Call: ( 9) mystere1([5, 4, -3], _L168) ? s
Exit: ( 9) mystere1([5, 4, -3], [5, 4]) ?
Call: ( 9) mystere2([5, 4], _G200) ? s
Fail: ( 9) mystere2([5, 4], _G200) ?
Redo: ( 11) mystere1([-3], _G313) ?
Call: ( 12) mystere1([], _G316) ?
Exit: ( 12) mystere1([], []) ?
Exit: ( 11) mystere1([-3], [-3]) ?
90
Exit: ( 10) mystere1([4, -3], [4, -3]) ?
Exit: ( 9) mystere1([5, 4, -3], [5, 4, -3]) ?
Call: ( 9) mystere2([5, 4, -3], _G200) ?
Call: ( 10) filtre(5) ?
^ Call: ( 11) _L193 is 5 mod 2 ?
^ Exit: ( 11) 1 is 5 mod 2 ?
Call: ( 11) 1=0 ?
Fail: ( 11) 1=0 ?
Fail: ( 10) filtre(5) ?
Fail: ( 9) mystere2([5, 4, -3], _G200) ?
Fail: ( 8) pred1([5, 4, -3], _G200) ?
No
pred([5,4,−3],X)
mystere2(Z,X)
mystere1([5,4,−3],Z)
X = [5|ResteFiltre]
Z = [5|Z1]
filtre(5) mystere2([4],ResteFiltre)
mystere1([4,−3],Z1)
Z1 = [4|Z2]
ECHEC
mystere1([−3],Z2)
−3 < 0 mystere1([],Z2)
Z = []
mystere1([],[])
On voit qu’il n’y a qu’un seul point de choix : le but mystere1([-3], Z2)
(qui correspond au but mystere1([-3], _G313) dans la trace). L’interprète
effectue un retour arrière à ce point de choix, et ajoute le but mystere1([],
91
Z3) dans la résolvante. On obtiendra alors la liste Z3, tel qu’illustré à la figure 10.
pred([5,4,−3],X)
mystere2(Z,X)
mystere1([5,4,−3],Z)
Z = [5|Z1]
mystere1([4,−3],Z1)
Z1 = [4|Z2]
mystere1([−3],Z2)
Z2 = [4|Z3]
mystere1([],Z3)
Z3 = []
mystere1([],[])
92