Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et...

101
Programmation logique Michel Gagnon École Polytechnique de Montréal 17 janvier 2006 Table des matières 1 Introduction à la programmation logique 3 1.1 Concepts de base .......................... 3 1.2 Definition de prédicats ....................... 4 1.3 Requêtes ............................... 5 1.4 Clauses ............................... 7 1.5 Exercices .............................. 9 2 Variables et unification (pour extraires les informations) 10 2.1 Algorithme d’unification ...................... 10 2.2 Extraction de l’information ..................... 12 2.3 Variables anonymes ......................... 14 2.4 Opérateur d’unification ....................... 15 2.5 Exercices .............................. 15 3 Algorithme d’exécution d’un programme 16 3.1 Algorithme de résolution ...................... 16 3.2 Exemple d’exécution ........................ 17 3.3 Retour arrière avec plusieurs points de choix ............ 23 3.4 Exercices .............................. 26 4 Opérateurs arithmétiques 27 4.1 Opérateurs .............................. 27 4.2 Exercices .............................. 29 1

Transcript of Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et...

Page 1: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Programmation logique

Michel GagnonÉcole Polytechnique de Montréal

17 janvier 2006

Table des matières1 Introduction à la programmation logique 3

1.1 Concepts de base . . . . . . . . . . . . . . . . . . . . . . . . . . 31.2 Definition de prédicats . . . . . . . . . . . . . . . . . . . . . . . 41.3 Requêtes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.4 Clauses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2 Variables et unification (pour extraires les informations) 102.1 Algorithme d’unification . . . . . . . . . . . . . . . . . . . . . . 102.2 Extraction de l’information . . . . . . . . . . . . . . . . . . . . . 122.3 Variables anonymes . . . . . . . . . . . . . . . . . . . . . . . . . 142.4 Opérateur d’unification . . . . . . . . . . . . . . . . . . . . . . . 152.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

3 Algorithme d’exécution d’un programme 163.1 Algorithme de résolution . . . . . . . . . . . . . . . . . . . . . . 163.2 Exemple d’exécution . . . . . . . . . . . . . . . . . . . . . . . . 173.3 Retour arrière avec plusieurs points de choix . . . . . . . . . . . . 233.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

4 Opérateurs arithmétiques 274.1 Opérateurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.2 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

1

Page 2: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

5 Listes 295.1 Définitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295.2 Unification de listes . . . . . . . . . . . . . . . . . . . . . . . . . 305.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

6 Processus récursif 326.1 Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326.2 Vérification de l’appartenance à une liste . . . . . . . . . . . . . . 336.3 Concaténation de listes . . . . . . . . . . . . . . . . . . . . . . . 386.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

7 Interprétations déclarative et procédurale 44

8 Construction de nouvelles structures 488.1 Appariement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488.2 Utilisation d’un accumulateur : . . . . . . . . . . . . . . . . . . . 508.3 Autres exemples : . . . . . . . . . . . . . . . . . . . . . . . . . . 518.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

9 Ordre des clauses 56

10 Manipulation dynamique de programmes 69

11 Recupération de solutions multiples 7111.1 Utilisation du retour arrière . . . . . . . . . . . . . . . . . . . . . 7111.2 Prédicat findall . . . . . . . . . . . . . . . . . . . . . . . . . 7211.3 Prédicats bagof et setof . . . . . . . . . . . . . . . . . . . . . 7411.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76

12 Mécanismes de contrôle 7612.1 Coupe-choix : . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7612.2 Dangers avec le coupe-choix . . . . . . . . . . . . . . . . . . . . 8512.3 Négation : . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8712.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

13 Exemples additionnels 8913.1 Tours de Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . 8913.2 Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9013.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91

2

Page 3: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

14 Listes de différence 9114.1 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

15 Quelques prédicats prédéfinis 9415.1 Vérification du type d’un terme . . . . . . . . . . . . . . . . . . . 9515.2 Comparaison et unification de termes . . . . . . . . . . . . . . . . 97

16 Conseils pour bien programmer en Prolog 9716.1 Erreurs fréquentes . . . . . . . . . . . . . . . . . . . . . . . . . . 9716.2 Trace d’exécution . . . . . . . . . . . . . . . . . . . . . . . . . . 97

1 Introduction à la programmation logique1.1 Concepts de base

Écrire un programme en Prolog consiste essentiellement à définir un ensembled’entités et des relations entre ces entités. Pour se référer à une entité, on utilisedes termes. Un terme est une constante, une variable ou un terme composé :

– Constante : Désigne une entité du monde. Tout identificateur qui commencepar une lettre minuscule est une constante. Si on veut utiliser une constantequi commence par une lettre majuscule, il faudra la délimiter par des apos-trophes.Exemples : paulo, 3, ’UFPR’.

– Variable : En Prolog, une variable sert à désigner une entité inconnue maisqui existe. Aussitôt que cette entité devient connue (on dira alors que la va-riable est instanciée), elle sera toujours associée à celle-ci. Ceci est différentdu concept de variable dans les langages traditionnels de programmation(comme C++), où le contenu d’une variable peut changer.En Prolog, une variable est n’importe quel identificateur qui commence parune lettre majuscule. Une variable peut aussi commencer par le symbole "_"(il s’agira alors d’une variable anonyme, comme on le verra plus tard).

– Terme composé : Formé par une constante fonctionnelle (aussi appeléefoncteur) et des arguments qui sont eux-mêmes des termes, il sert à dési-gner indirectement une entité du monde.Par exemple, en utilisant un foncteur suc et la constante 0, on peut repré-senter les nombres naturels. La valeur 1, qui est le successeur de 0, serait re-présentée par le terme suc(0). Le numéro 3 serait représenté par le termesuc(suc(suc(0))).L’expression (8−4)∗3 pourrait être représentée par le terme mult(moins(8,4),3).

3

Page 4: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

un terme composé est essentiellement récursif, puisqu’il contient des argu-ments qui sont eux-même des termes.Notons qu’une variable peut apparaître dans un terme composé. Supposonspar exemple que nous utilisons le foncteur pere_de pour représenter lepère d’une personne. Ainsi, le terme pere_de(maria) représente le pèrede Maria. Rien n’empêche d’écrire le terme pere_de(X), qui désigne unentité qui est le père de quelqu’un.

1.2 Definition de prédicatsPour 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 à certainsarguments (les arguments d’un prédicat sont des termes). Si un prédicat accepteseulement un argument, on dit qu’il définit une propriété. Par exemple, l’expressionintelligent(michel) représente une proposition qui sera vraie si Michel estintelligent, et fausse dans le cas contraire.

Si un prédicat a plus d’un argument, il exprime une relation entre des entités dumonde. 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 termecomposé, comme dans acheter(laura, appartement(145, edifice(a))).Il peut aussi être une variable, comme dans mere(X, marie_berthe). Cettedernière expression peut être comprise de la manière suivante : Marie-Berthe est lamè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 avecses arguments, représente une propriété ou une relation qui peut être vraie oufausse. Mais comment déterminer cette valeur de vérité ? Une manière simple dele faire consiste à déclarer un fait. Ainsi, en Prolog, pour indiquer que Michelest 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 écrireen Prolog. Voici un exemple de programme :

homme(paulo).homme(joao).homme(francisco).femme(maria).femme(roberta).

4

Page 5: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

femme(ana).pere(francisco,paulo).pere(joao,paulo).pere(ana,francisco).mere(rita,roberta).mere(joao,roberta).mere(francisco,roberta).mere(roberta,maria).

Remarquez que pere n’est pas un foncteur, mais plutôt un prédicat. Il n’estpas 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 estvraie 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 estd’être consistant dans l’usage qu’on fait d’un prédicat. Si le programmeur écritpere(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 lesecond argument est le père de celle exprimée par le premier argument. Ainsi, dansnotre exemple, Paulo est le père de Francisco.

1.3 RequêtesEn 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 ?-. Prologrépondra Yes si elle est vraie, et No dans le cas contraire. Avec le programmesimple présenté plus haut, on peut faire diverses requêtes. On peut vérifier si Mariaest une femme, ou encore si João est le père de Paulo :

?- femme(maria).Yes?- pere(paulo,joao).No

Une requête peut contenir des variables. Pour comprendre ce qui se produitdans ce cas, il faut d’abord définir deux concepts importants :

Substitution : Une substitution θ est un ensemble fini (possiblement vide) depaires de la forme Xi = ti, où Xi est une variable et ti est un terme, qui respecteles contraintes suivantes :

5

Page 6: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

– Pour tout Xi = ti ∈ θ et Xj = tj ∈ θ, où i 6= j, on ne peut pas avoirXi = Xj .

– Soit Xi = ti ∈ θ. Pour tout Xj = tj ∈ θ, Xi ne peut pas apparaître dans tj .Par exemple, les ensembles {X = paulo, Y = 2} et {X = paulo, Y = f(Z)} sont

des substitutions. L’ensemble {X = paulo, Z = f(X)} n’est pas une substitution lé-gale, parce que la variable X ne peut pas apparaître dans un autre terme. L’ensemble{X = paulo, Y = 2, X = maria} n’est pas une substitution parce que X apparaît deuxfois à gauche du signe =. Remarquez que {X = p(Z), Y = Z} est une substitutionvalide.

Normalement, une substitution est appliquée à un terme composé ou une ex-pression prédicative. L’effet de l’application d’une substitution θ à une expressionA, 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 θ. Parexemple, 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’ilexiste une substitution θ telle que A = θB. Par exemple, p(paulo,Y) estune instance de p(X,Y). De même, p(paulo,maria) est une instance dep(X,Y).

Considérons maintenant la requête suivante :

?- homme(X).

En utilisant le programme ci-dessus, cette requête réussira s’il existe un hommedans les faits qui sont déclarés dans le programme. De plus, l’interpréteur Prologretournera une substitution pour toutes les variables qui apparaissent dans la re-quête. Dans ce cas-ci, cela revient à identifier l’entité qui satisfait le prédicat. Dansnotre programme, il y a trois possibilités. Prolog retournera la première :

?- 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 :

6

Page 7: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- homme(X).X = pauloYes

Si nous entrons ’ ;’ (le point-virgule), il tentera de retourner une autre réponse :

?- homme(X).X = paulo ;X = joao

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’autrespossibilité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’elleest 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(X,roberta),pere(X,paulo).X = joao ;X = francisco ;No

1.4 ClausesSi 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 faitde manière indirecte. Par exemple, une personne est propriétaire d’un objet si ellea acheté cet objet. Dans ce cas, nous utilisons une clause, qui définit un prédicat enfonction d’autres prédicats :

7

Page 8: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

proprietaire(X,Y):-acheter(X,Y).

Cette clause doit être comprise de la manière suivante : pour savoir si X est lepropritétaire de Y, il faut vérifier si X a acheté Y. Il existe une autre manière d’êtrepropriétaire d’un objet : le recevoir. Ainsi, notre programme peut être étendu pourtenir compte de cette seconde possibilité :

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

Maintenant, nous pouvons soumettre les requêtes suivantes :

?- proprietaire(paulo,auto).Yes

?- proprietaire(paulo,manteau).No

?- proprietaire(maria,X).X = manteau

?- proprietaire(X,Y).X = mariaY = manteau ;

X = pauloY = auto ;

8

Page 9: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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.

Rien n’empêche de mélanger des faits avec des clauses. Par exemple, dans leprogramme, on peut spécifier directement que Ana est propriétaire d’un chien quis’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 devérité, et ’terme composé’, qui désigne une entité du monde.

1.5 Exercices1.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).

9

Page 10: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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

?- situe_dans(florianopolis,parana).

?- situe_dans(X,santa_catarina).

?- situe_dans(curitiba,X).

?- situe_dans(X,Y).

Quelle est la réponse à la requête suivante ?situe_dans(santa_catarina,bresil)? Pourquoi ?

1.2 Écrivez les clauses Prolog qui définissent les relations suivantes :

grand_parent(X,Y) (Y est la grand-mère ou le grand-père de X)cousin(X,Y) (X e Y sont cousins germains)

2 Variables et unification (pour extraires les informations)2.1 Algorithme d’unification

Pour bien comprendre l’algorithme utilisé par Prolog pour exécuter un pro-gramme, il faut d’abord comprendre l’unification. Intuitivement, l’unification consisteen une tentative pour déterminer s’il existe une manière d’instancier deux expres-sions afin de les rendre égales. Plus formellement, l’unification de deux termes A

et B est une substitution θ telle que θA = θB. On dénote θ l’unificateur de A etB. Par exemple, la substitution {Y = joao, X = mere(joao)} est un unificateur desénoncés aime(joao,X) et aime(Y,mere_de(Y)) .

En Prolog, l’opérateur "=" désigne l’unification. Voici un exemple :

?- aime(joao,X) = aime(Y,mere_de(Y)).X = mere(joao)Y = joao

10

Page 11: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Il y a deux détails importants à connaître sur l’utilisation de l’unification enProlog. Premièrement, quand l’interprète Prolog tente d’unifier un but avec unfait ou la tête d’une clause, il peut s’avérer nécessaire de renommer les variables.Supposons par exemple le fait aime(X,joao) et le but aime(maria,X). Ilspeuvent êtres unifiés si on renomme les variables. Dans ce cas, après le renommage,l’unification sera effectuée avec les énoncés aime(X1,joão)et aime(maria,X2),ce qui donnera le résultat {X1 = maria, X2 = joao}.

L’autre détail important est illustré par l’exemple suivant d’unification, qui peutretourner 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’unificateurle 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 leplus 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 retourneECHEC sinon :

11

Page 12: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

θ = ∅La pile contient l’équation A = B

Tant que pile non vide :Retirer la première équation X = Y de la pileSi :

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 θ

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. Chaqueargument du prédicat de A doit s’unifier avec l’argument à la même position dansB. Rappelons-nous que chaque argument est lui-même un terme, qui peut être unevariable, une constante ou un terme composé. Deux termes s’unifient si une destrois conditions suivantes est respectée :

– Les deux termes sont des constantes identiques.– Un des termes est une variable non instanciée. Dans ce cas la variable sera

instanciée par l’autre terme.– Les deux termes respectent les conditions suivantes : :

– Ils ont le même foncteur et le même nombre d’arguments.– Les arguments s’unifient.

2.2 Extraction de l’informationVoyons maintenant comment l’unification permet d’extraire de l’information.

Soit le même programme simple que nous avons introduit à la section précédente :

homme(paulo).homme(joao).

12

Page 13: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

homme(francisco).femme(maria).femme(roberta).femme(ana).pere(francisco,paulo).pere(joao,paulo).pere(ana,francisco).mere(rita,roberta).mere(joao,roberta).mere(francisco,roberta).mere(roberta,maria).

Pour savoir qui est une femme, il suffit de faire la requête suivante :

?- femme(X).X = maria.

Pour retourner la réponse, l’interprète Prolog tente d’unifier le but femme(X)avec chacun des faits que le programme contient, dans l’ordre de leur apparition.L’unification avec le fait homme(paulo) échoue, puisque les deux prédicats sontdifférents. Il en sera de même pour les deux faits qui suivent. C’est seulementlorsque 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 laconstante maria. Voici d’autres exemples :

?- pere(X,paulo).X = francisco ;X = joao ;No

?- pere(X,Y).X = franciscoY = paulo ;

X = joaoY = paulo ;

X = anaY = francisco ;No

13

Page 14: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

2.3 Variables anonymesUne variable anonyme est une variable dont la valeur instanciée après unifi-

cation nous importe peu. Une variable anonyme est n’importe quel identificateurcommençant par le symbole "_". Par exemple, si on désire seulement savoir siPaulo a un fils, on peut faire la requête suivante :

?- pere(_,paulo).Yes

Deux occurrences d’une variable anonyme dans un prédicat sont distinctes. Parexemple, si nous désirons seulement savoir s’il existe une mère, nous soumettronsla requête suivante :

?- mere(_,_).Yes

Il est clair ici qu’il ne s’agit pas de trouver toute personne qui est sa propremère. En d’autres mots, cette requête n’est pas équivalente à celle-ci :

?- mere(X,X).No

En fait, ce que l’interpréteur Prolog fait, c’est un renommage de chaque va-riable anonyme pour la distinguer des autres. Cela signifie que la requête mere(_,_)est transformée en une requête de la forme mere(_G120,_G304).

Jusqu’à maintenant, aucun des faits que nous avons déclarés ne contenait pasde variables. Il n’est pas nécessaire qu’il en soit ainsi. Supposons que nous écrivonsun programme pour spécifier qui aime qui :

aime(paulo,maria).aime(ana,clara).aime(claudia,paulo).

Si nous voulons spécifier que tout le monde aime Maria, nous ajouterions lefait suivant,qui contient une variable anonyme :

aime(_,maria).

Si nous voulons spécifier que tout le monde s’aime soi-même, nous ajouterionsle fait suivant :

aime(X,X).

14

Page 15: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

2.4 Opérateur d’unificationOn peut utiliser directement l’opérateur "=" pour unifier deux termes (ou deux

expressions prédicatives) :

?- X = 1.X = 1

?- joao = roberta.No

?- suc(X) = suc(suc(suc(0))).X = suc(suc(0))

?- p(X,X) = p(1,2).No

?- p(X,X) = p(1,1).X = 1

?- p(X,X) = p(1,W).X = 1W = 1

?- p(Y,fun(Y)) = p(toto,Z).Y = totoZ = 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 Exercices2.1 Quel est le résultat des tentatives d’unification suivantes :

UFPR = ’UFPR’ufpr = ’UFPR’

15

Page 16: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

ufpr = ’ufpr’4 = 3+1f(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))

2.2 Soit le programme suivant :

aime(paulo,maria).aime(ana,clara).aime(claudia,paulo).

Pourquoi ne pouvons-nous pas ajouter le fait aime(_,_) pour spécifier quetout le monde s’aime soi-même ? Expliquez ce qui se produit lorsqu’on ajoute cefait.

3 Algorithme d’exécution d’un programme3.1 Algorithme de résolution

Essentiellement, un programme Prolog est composé de règles, qui sont desclauses déclarant un fait qui dépend d’autres faits. Ces règles ont la forme suivante :

H :- B1,B2, . . .,Bn

H et B1,B2, . . .,Bn constituent la tête et le corps de la règle, respectivement.La signification d’une règle est la suivante : pour résoudre un but H, il faut résoudrela conjonction de buts B1,B2, . . .,Bn. Un fait peut être considéré comme uneclause sans corps.

Pour satisfaire une requête, l’interpréteur Prolog utilise l’algorithme de résolu-tion, qui utilise une pile, appelée résolvante, pour contenir tous les buts qu’il resteà résoudre (notez que nous considérons ici qu’un fait est une clause dont le corpsest vide) :

16

Page 17: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

0) Se positionner à la première clause du programme1) Résolvante← requête2) Tant que la résolvante n’est pas vide :

2.1) Obj ← Premier objectif de la résolvante2.2) Retirer le premier objectif de la résolvante2.3) Chercher la première clause C = H : −B1, B2, . . . Bn telle que H s’unifie avec Obj

Si on réussit2.4) Ajouter au début de la résolvante tous les buts B1,B2,. . .Bn

2.5) Si il existe d’autres têtes de clauses unifiables avec Obj

Mémoriser la position de la clause C comme dernier point de choix2.6) Retourner à 2.1

Sinon2.7) Si il existe des points de choix :

Retourner au dernier point de choix (Backtrack)Revenir à l’étape 2.3

Sinon :Retourner ECHEC

3) Retourner l’instantiation des variables de la requête

Il est important de noter que dans ce processus, le système mémorise les va-riables instanciées. Quand une variable est instanciée dans une règle, soit dans latête, soit dans le corps, la même instanciation s’applique à toutes les occurrencesde la variable dans la règle. Quand l’interpréteur fait un retour arrière à un point dechoix, il élimine le résultat de toutes les unifications qui ont été réalisées entre lemoment où le point de choix a été mémorisé et le moment de l’échec.

3.2 Exemple d’exécutionSoit le programme suivant (à noter qu’on doit interpréter le prédicat pere de

telle manière que le second argument représente la père de la personne indiquéepar le premier argument) :

homme(paulo).homme(joao).homme(francisco).femme(maria).femme(roberta).femme(ana).pere(francisco,paulo).pere(joao,paulo).pere(ana,francisco).mere(rita,roberta).mere(joao,roberta).mere(francisco,roberta).

17

Page 18: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

mere(roberta,maria).parents(X,M,P):-

mere(X,M),pere(X,P).

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êtede la dernière clause du programme. Ceci entraînera l’ajout des trois buts suivantsdans 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. Enrenommant 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 larésolvante :

mere(francisco,M1)pere(francisco,P1)parents(francisco,M,P)

Le premier but est résolu en l’unifiant avec le fait mere(francisco,roberta),qui se trouve dans le programme. Cette unification ajoute l’instanciation M1 =roberta. Similairement, le second but est résolu avec le fait pere(francisco,paulo),où P1 = paulo. La résolvante n’aura alors qu’un seul but à résoudre :

parents(francisco,roberta,paulo)

18

Page 19: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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.

Finalement, le dernier but est résolu de manière triviale, puisqu’il existe un faitidentique dans le programme. La résolution se termine donc avec succès, avec lasubstitution suivante : M = roberta, P = paulo.

La figure 1 montre les mêmes étapes de la résolution sous forme d’un arbre. Nousutiliserons dorénavent cette représentation pour illustrer la résolution.Considérons maintenant la requête suivante :

?- frere(francisco,Z).X = joao

Comme nous le verrons, la résolution de ce but exigera un retour arrière. Lafigure 2 montre la résolution jusqu’à l’obtention de l’échec. Quand cet échec seproduit, il faut reculer au dernier point de choix. Dans ce cas, il s’agit du moment oùétait tentée la résolution du but homme(Y). Reculant à ce point, et en poursuivantla recherche d’une clause s’unifiant à ce but, on obtient une nouvelle instanciation(Y = joao), et la résolution continue à partir de ce point. La figure 3 la suite dela résolution.

Maintenant, nous sommes en mesure de comprendre l’effet du " ;" entré aprèsune 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, lerésultat serait le suivant :

?- frere(francisco,X).X = joao ;X = francisco ;No

Pour fournir cette seconde solution, l’interprète Prolog recule jusqu’au pointde choix qui avait été ajouté lors de la résolution du but homme(Y). On voit quecette technique est pratique pour connaître, dans cet exemple, tous les frères d’unepersonne. Mais on voit aussi que notre programme a le défaut de nous indiquer quechaque homme est le frère de soi-même. Pour éviter cela, il faudrait modifier laclause qui définit le prédicat frere, en ajoutant un test supplémentaire :

19

Page 20: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

homme(francisco) parents(joao,M,P) parents(francisco,M,P)

frere(joao,francisco)

frere(joao,francisco)

homme(francisco) parents(joao,M,P) parents(francisco,M,P)

P=P1M=M1

mere(joao,M1) pere(joao,P1)

frere(joao,francisco)

homme(francisco) parents(joao,M,P) parents(francisco,M,P)

P=P1M=M1

mere(joao,M1) pere(joao,P1)

mere(joao,roberta) pere(joao,paulo)

P1=pauloM1=roberta

frere(joao,francisco)

homme(francisco) parents(joao,M,P) parents(francisco,M,P)

P=P1M=M1

mere(joao,M1) pere(joao,P1)

mere(joao,roberta) pere(joao,paulo)

mere(francisco,roberta)

M1=roberta P1=paulo

pere(francisco,paulo)

FIG. 1 – Arbre de résolution

20

Page 21: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

frere(francisco,Z)

Z=Y

parents(francisco,M,P) parents(Y,M,P)homme(Y)

frere(francisco,Z)

homme(Y) parents(francisco,M,P) parents(Y,M,P)

homme(paulo)

Y=paulo

Z=Y

frere(francisco,Z)

Z=Y

homme(Y) parents(francisco,M,P) parents(Y,M,P)

P=P1

pere(francisco,P1)mere(francisco,M1)

M=M1Y=paulo

homme(paulo)

frere(francisco,Z)

Z=Y

parents(Y,M,P)parents(francisco,M,P)homme(Y)

Y=pauloP=P1

mere(francisco,M1)

M1=roberta

mere(francisco,roberta)

pere(francisco,P1)

P1 = paulo

pere(francisco,paulo)

M=M1

homme(paulo)

frere(francisco,Z)

Z=Y

parents(francisco,M,P)homme(Y)

Y=pauloP=P1

mere(francisco,roberta) pere(francisco,paulo) ÉCHEC

parents(Y,M,P)

homme(paulo) mere(francisco,M1) pere(francisco,P1) mere(paulo,roberta) pere(paulo,paulo)

M=M1

M1=roberta P1=paulo

FIG. 2 – Arbre de résolution

21

Page 22: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

frere(francisco,Z)

Z=Y

parents(francisco,M,P) parents(Y,M,P)homme(Y)

Y=joao

homme(joao)

frere(francisco,Z)

Z=Y

parents(francisco,M,P) parents(Y,M,P)

P=P1

mere(francisco,M1)

homme(Y)

Y=joao

homme(joao)

M=M1

pere(francisco,P1)

frere(francisco,Z)

Z=Y

parents(Y,M,P)homme(Y)

Y=joao

parents(francisco,M,P)P=P1

mere(francisco,M1)

M1=roberta

mere(francisco,roberta)

homme(joao)

M=M1

pere(francisco,P1)

pere(francisco,paulo)

P1=paulo

frere(francisco,Z)

Z=Y

homme(Y) parents(francisco,M,P) parents(Y,M,P)

Y=joaoP=P1

pere(francisco,paulo)mere(francisco,roberta)

M=M1

homme(joao) mere(francisco,M1) pere(francisco,P1) mere(joao,roberta) pere(joao,paulo)

M1=roberta P1=paulo

FIG. 3 – Arbre de résolution

22

Page 23: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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écutiond’un programme. Supposons, par exemple, que le prédicat frere soit défini de lamaniè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êtecomme frere(francisco,X), le programme pourrait tenter inutilement derésoudre le but parents(Y,M,P) en instanciant Y par une valeur qui représenteune femme.

3.3 Retour arrière avec plusieurs points de choixQuand il y a plus d’un point de choix au moment du retour arrière, il faut

s’assurer d’identifier celui qui sera considéré en premier. On reculera au point dechoix correspondant au dernier but unifié pour lequel il restait d’autres possibilitésd’unification. Pour mieux comprendre, considérons le programme suivant :

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):-

23

Page 24: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

homme(X).humain(X):-

femme(X).

professeur_ia(michel).debrouillard(chico).dauphin(flipper).femme(george).homme(chico).sympathique(flipper).

Supposons maintenant la requête suivante :

?- tres_intelligent(X).

Pour résoudre ce but, l’interpréteur Prolog devra réaliser plusieurs retours ar-rière. La figure 4 montre l’état de l’exécution la première fois qu’un échec se pro-duit (les points de choix sont montrés en italiques).

tres_intelligent(X)

intelligent(X1)

OK

humain(chico)

homme(chico)

OK

X = chico

X1 = X2

X = X1

debrouillard(X2) ECHEC

sympatique(X1)

FIG. 4 – Arbre de résolution

24

Page 25: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

On note l’existence de trois points de choix. L’ordre d’apparition de ces pointsde choix est le suivant :

1. tres_intelligent(X)2. intelligent(X)3. humain(X)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 sebuter une nouvelle fois à un échec (voir figure 5).

sympathique(X1)

tres_intelligent(X)

intelligent(X1)

OK

X = chico

femme(chico)

humain(chico)

ECHEC

X=X1

X1=X2

debrouillard(X2)

FIG. 5 – Arbre de résolution

Cette fois, l’interprète Prolog reculera au point de choix associé au but intelligent(X).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 alorscontiner et terminer avec succès dans l’état illustré à la figure 6.

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 dechoix. Dans ce cas, une nouvelle résolution à partir de ce point tentera de résoudrele but professeur_ia(X), ce qui se terminera avec succès, avec X = michel.

25

Page 26: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

sympathique(flipper)

tres_intelligent(X)

dauphin(X2)

OK

X2=flipper

OK

intelligent(X1)

X1 =X2

X = X1

FIG. 6 – Arbre de résolution

3.4 Exercices3.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.3.2 Supposons un édifice contenant un ensemble de salles. On utilise une relationconnexe(X,Y) pour indiquer que l’on peut passer directement de la salle X à lasalle Y, et vice versa. Supposant que la constante exterieur désigne l’extérieurde l’édifice, définissez le prédicat sortir(X), dont la résolution réussit s’il existeun 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).

26

Page 27: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

connexe(salle5,salle8).connexe(salle6,salle9).connexe(salle9,exterieur).

?- sortir(salle1).Yes

?- sortir(salle4).No

4 Opérateurs arithmétiques4.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 grandou égal", respectivement.

Pour utiliser ces opérateurs, les variables doivent être instanciées :

?- 4 >= 3.Yes

?- X >= 3.No

Prolog fournit aussi les opérateurs arithmétiques +, -, *, /, mod. Pourrécupérer le résultat d’une opération arithmétique en Prolog, on utilise l’opérateur"is" : X is Exp . La valeur de l’expression (composée en utilisant les opéra-teurs arithmétiques) est calculée et on instancie la variable X avec le résultat. Sil’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 = 4Y = 8

27

Page 28: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Si l’opérateur is est utilisé avec à sa gauche une variable instanciée, l’inter-préteur Prolog vérifiera tout simplement si les valeurs sont égales :

?- X = 7, X is 3 + 4.X = 7Yes

?- X = 6, X is 3 + 4.No

Il est important de noter que l’expression à la gauche de l’opérateur is ne peutêtre qu’une valeur numérique, et non pas une expression quelconque. Par exemple,la requête suivante échoue, malgré l’égalité :

?- X = 5 + 2, X is 3 + 4.No

L’utilisation du résultat d’une expression numérique se fait indirectement parle biais d’une variable.

Exemple : Programme qui convertit en valeur numérique un structure suc(suc(...)) :

Erroné:convertir(0,0).convertir(suc(X),Y+1):-

convertir(X,Y).

Correct: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 à chaquepas de l’exécution. Il construit un structure en utilisant l’opérateur + :

?- convertir(suc(suc(0)),X).X = 0 + 1 + 1

Seul le second programme évalue le résultat de l’expression :

?- convertir(suc(suc(0)),X).X = 2

28

Page 29: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 Exercices4.1 Definissez un programme puissance(X,N,P) qui unifie P avec la va-

leur XN .

?- puissance(2,3,X).X = 8

5 Listes5.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 :

.(1, .(2, .(3, [])))

Il existe une autre notation plus pratique : [1,2,3]Une liste peut contenir des éléments de types différents :

[1, 22, X, 54][4, plus(3,plus(3,0)), a]

Une liste peut contenir d’autres listes :

[1, [5,6], 3,10][X1, roberto, [ [paulo, Y], andre], eliana]

29

Page 30: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Voici une autre notation qui distingue la tête du reste de la liste (aussi appelé queuede la liste) :

[1 | [2,3] ]⇔ .(1, [2,3])⇔ [1,2,3]

Il est possible d’isoler plus d’un élément en tête de liste. Par exemple, le termesuivant s’unifie avec n’importe quelle liste qui commence par les éléments 1 et 2 :

[1,2|X]

5.2 Unification de listesVoici des exemples d’unification de listes :

?- [1,2|X] = [1,2]X = []

?- [1,2|X] = [1,2,7,3,4]X = [7,3,4]

Dans un programme Prolog, on utilise très souvent la décomposition d’une listepour isoler la tête. Voici des exemples :

p([1,2,3]).p([je,mange,[un,gateau,au,chocolat]]).

?- p([X|Y]).X = 1Y = [2,3] ;

X = jeY = [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 quisuit, X s’unifie avec le premier élément de la liste [4], qui est la constante 4. Ydoit être unifié avec le reste de la liste. Comme il n’y a aucun élément après 4, ilsera donc unifié avec la liste vide.

30

Page 31: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- [X|Y] = [4].X = 4Y = []

Voici un autre exemple du même phénomème, mais de manière indirecte parl’unification de deux prédicats :

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

Voici un exemple subtil d’échec à l’unification des termes suivants :

?- 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. CommeZ a déjà été instanciée par le terme [X,Y], il faudra alors unifier celui-ci avec leterme [X|Y]. Les deux têtes s’unifient sans problème, puisqu’il s’agit de la mêmevariable 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 autreliste dont l’unique élément est la liste elle-même, résultant ainsi en une structureinfinie :

Y = [[[[...]]]]

Voici un autre exemple d’unification :

?- [X,a|Z] = [b,a,a|K]X = bZ = [a|K]

Ici, nous tentons d’unifier une liste qui contient au moins deux éléments avecune 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 variableZ.

31

Page 32: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

5.3 Exercices5.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écursif6.1 Definition

En Prolog, on utilise de manière systématique la récursivité pour résoudre unproblème. Un processus récursif consite en :

– un pas récursif qui accomplit une partie du travail (normalement sur un mor-ceau de la structure manipulée) et qui appelle récursivement la résolution dumê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.

Normalement, un processus récursif agit sur une structure elle-même récursive(c’est-à-dire un terme dont l’un des arguments est un terme du même type). Voiciun exemple de prédicat défini de manière récursive :

factoriel(0,1).factoriel(X,F):-

Y is X-1,factoriel(Y,Fy),

32

Page 33: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

F is Fy * X.

?- factoriel(4,X).X = 24

?- factoriel(X,24).Error

La seconde requête échoue parce que le programme n’a pa été écrit de manièreà accepter une variable libre comme premier argument. Ce qui se produit dans cecas est que la résolution du but Y is X-1, dans le corps de la seconde clause, nesera pas possible puisque la variable X doit être instanciée, comme l’exige l’opéra-teur is.

6.2 Vérification de l’appartenance à une listeNous verrons maintenant la définition du prédicat member, déjà pré-défini

en Prolog, qui vérifie si un item est contenu dans une liste. Le raisonnement estsimple : si l’item recherché est le premier de la liste, on retourne avec succès.Sinon, on continue la recherche dans la queue de la liste :

member(X,[X|_]).member(X,[_|Y]):-

member(X,Y).

?- member(4,[2,3,4,8]).Yes

Il est intéressant de savoir qu’on peut utiliser le même prédicat pour extraireséquentiellement les éléments de la liste. Lors de la résolution, le premier élémentde 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

33

Page 34: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

OK

X = 2

Première solution retournée:

Deuxième solution retournée:

member(X,[2,3,4])

X = X1

OK

X1 = 3

member(X,[2,3,4])

member(X1,[3,4])

Dernière tentative:

member(X,[2,3,4])

member(X1,[3,4])

member(X2,[4])

X = X1

X1 = X2

X2 = X3

Troisième solution retournée:

member(X,[2,3,4])

member(X1,[3,4])

member(X2,[4])

X = X1

X1 = X2

OK

X2 = 4

member(X3,[])

ÉCHEC

FIG. 7 – Résolution de member(X,[2,3,4]).

34

Page 35: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

La figure 7 montre les détails de la résolution de cette requête (les points dechoix sont indiqués en italique).

La définition du prédicat membermontre que l’unification permet l’utilisationde techniques très puissantes, qui sont souvent utilisées dans l’élaboration de pro-grammes Prolog :

– La décomposition de la liste (en isolant la tête) peut être spécifiée dans latête de la clause. Dans la première clause de member, on isole le premierélément et on utilise une variable anonyme pour la queue, étant donné qu’ellene nous intéresse pas dans ce cas. Dans la seconde clause, nous avons le cascontraire : on utilise une variable anonyme pour la tête de liste, alors que lereste sera unifié avec la variable Y.

– L’identité des éléments peut être spécifiée dans la tête de la clause. Parexemple, dans la première clause de member, on exige que l’élément re-cherché s’unifie avec le premier de la liste.

Voici une autre définition de member, équivalente mais moins élégante :

member(X,[Y|_]):-X = Y.

member(X,[Y|Reste]):-X \= Y,member(X,Reste).

Il faut éviter ce type de définition. Il est en effet inutile d’utiliser des variablesdifférentes dans la tête de la première clause, puisque de toute façon on veut lesunifier.

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 contresi dans la requête le premier argument est une variable libre, le comportement estbien 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 aupoint de choix qui nous amène a faire une tentative avec la seconde clause. Mais

35

Page 36: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 demandeque ce ne soit pas le cas.

Pour bien saisir l’avantage de l’unification, on peut comparer avec ce que seraitune implémentation de member en C++. D’abord voici l’équivalent de l’implémen-tation des listes :

template <class T>class ListeElem {public:friend class Liste<T>;ListeElem(T h, ListeElem<T> * t) : head(h),tail(t) {}

private:T head;ListeElem<T> * tail;

};

template <class T>class Liste {public:Liste(ListeElem<T> * deb = NULL) : debutListe(deb) { }bool empty() { return debutListe == NULL; }T head() {return debutListe->head; }Liste<T> tail() { return Liste(debutListe->tail); }void insert(T elem){

if (debutListe == NULL)debutListe = new ListeElem<T>(elem, NULL );

elsedebutListe = new ListeElem<T>(elem, debutListe );

}private:

ListeElem<T> * debutListe;};

Avec cette implémentation des listes, nous définirions la fonction member de lamanière suivante :

template <class T>

36

Page 37: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

bool member(T x, Liste<T> l){

if (l.empty())return false;

else if (l.head() == x)return true;

elsereturn member(x, l.tail());

}

Nous allons maintenant voir un exemple où on utilise le prédicat member pourextraire les éléments d’une liste. Supposons une situation où on reçoit une listede noms de personnes et que nous voulons imprimer tous les éléments de cetteliste qui représentent des femmes. Considérons d’abord un programme récursif quisolutionne ce problème :

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]).anaclara

Yes

On peut faire une version non récursive du même programme, en utilisant leprédicat fail. Ce prédicat échoue toujours lorsqu’on essait de le résoudre, forçantainsi un retour arrière. Voici le programme.

liste_femme(L):-

37

Page 38: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

member(X,L),femme(X),write(X),nl,fail.

liste_femme(_).

À la première tentative de résolution de member, X s’unifie avec le premierélément de la liste. Ensuite, le programme vérifie si X est une femme. Si c’est lecas, le nom est affiché et on passe au prédicat suivant, qui est le fail. Un retourarrière est alors réalisé, ce qui nous ramène au point de choix qui a été introduitlors de la résolution de member. On obtiendra alors une nouvelle valeur pour X,soit le prochain item de la liste. Quand le prochain élément de la liste n’est pas unefemme, on échoue à la résolution du but femme(X), ce qui force à nouveau unretour arrière. Et ainsi de suite jusqu’à ce que toute la liste ait été parcourue.

6.3 Concaténation de listesLe prochain exemple est la concaténation de listes, représentée par le prédicat

append en Prolog. Le principe est le suivant :

1. Isoler le premier élément de la liste.2. Résoudre récursivement le prédicat pour concaténer le reste de la liste avec

la seconde liste.3. Ajouter au début de la liste résultante l’élément qui avait été isolé à la pre-

mière étape.

Traduit en Prolog, le programme obtenu est le suivant :

append([],X,X).append([X|Xs],Y,[X|Zs]):-

append(Xs,Y,Zs).

Ce qui est intéressant avec ce programme est que l’unification, puisqu’elle nefait pas de distinction entre les données et les résultats, permet différents usages dumême programme :

?- append([a,b,c],[d,e],R).R = [a,b,c,d,e]

?- append(X,[d,e],[a,b,c,d,e]).

38

Page 39: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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

La figure 8 montre les détails de la résolution de la dernière requête. La figure 9montre la situation au moment du premier retour arrière. Finalement, la figure 10montre l’état au second retour arrière.

append(X,Y,[a,b,c])

append([], [a,b,c],[a,b,c])

X=[]

Y = [a,b,c]

===> X=[] Y=[a,b,c]

FIG. 8 – Résolution de append(X,Y,[a,b,c])

Aussi étrange que cela puisse paraître, on peut même utiliser le prédicat appendavec des variables non instanciées pour les deux derniers arguments :

?- append([1,2],X,Y).X = KY = [1,2|K]

39

Page 40: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

append(X,Y,[a,b,c])

append([],[b,c],[b,c]) ===> X=[a] Y=[b,c]

Xs=[]Y1 = [b,c]

Y = Y1

X=[a|Xs]

append(Xs,Y1,[b,c])

append(X,Y,[a,b,c])

X=[a|Xs]

append(Xs,Y1,[b,c])

Y = Y1

FIG. 9 – Résolution de append(X,Y,[a,b,c])

append([],[c],[c]) ===> X=[a,b] Y=[c]

X1s = []

append(Xs1,Y2,[c])

append(Xs,Y1,[b,c])

append(X,Y,[a,b,c])

X=[a|Xs]

Xs=[b|Xs1]

append(X,Y,[a,b,c])

Y1 = Y2

Y = Y1

append(Xs,Y1,[b,c])

append(X1s,Y2,[c])

Y1 = Y2

Y = Y1

X=[a|Xs]

Xs=[b|X1s]

Y2 = [c]

FIG. 10 – Résolution de append(X,Y,[a,b,c])

40

Page 41: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

6.4 Exercices6.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 ensuivant l’algorithme suivant. Soit n la position d’une lettre dans l’alphabet. Chaquelettre 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 dsera substituée par la lettre g, et la lettre z par la lettre c. Le programme reçoit uneliste de lettres qui représente le nom et retourne une liste de lettres qui représentele nom encodé :

?- encoder([m,i,c,h,e,l],3,X).X = [p,l,f,k,h,o]

6.2 Soit le programme suivant :

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

a) Quel sera le résultat retourné pour la requête suivante :

?- mystere(4,L).

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

6.3 Soit le programme suivant :

41

Page 42: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

mystere(X,Y,Z):-myst1(X,Y,Z).

myst1([ ],[ ],[ ]).myst1([X|Reste],[X|R1],R2):-

myst2(Reste,R1,R2).

myst2([ ],[ ],[ ]).myst2([X|Reste],R1,[X|R2]):-

myst1(Reste,R1,R2).

a) Quel sera le résultat retourné pour la requête suivante :

?- mystere([t,b,u,e,d,m,o],X,Y).

b) Quel sera le résultat retourné pour la requête suivante :

?- mystere(X,[1],Z),

c) Quel sera le résultat si on demande une seconde solution pour la requête de b) ?

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

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. Dansl’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), quiinstancie 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

42

Page 43: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

6.5 Soit le programme suivant :

p(X,[],[X]).p(X,[Y|L],[X,Y|L]):-

X < Y.p(X,[Y|L],[Y|R]):-

p(X,L,R).

a) Quel sera le résultat retourné pour la requête suivante :

?- p(5,[2,3,9],Z).

b) Quel sera le résultat si on demande une seconde solution ?

6.6 Soit l’arbre généalogique suivant :

MÈRE

PÈRE

Pedro

Ana Luis

Maria

Jorge

Leo

Rui

PÈRE MÈRE

MÈRE

PÈRE

Une manière de représenter cet arbre est une liste de termes formés par lesfoncteurs pere et mere.

Définissez un prédicat descendant_communqui reçoit trois arguments. Lesdeux 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 :

?- 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),

43

Page 44: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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

6.7 Supposons une représentation en Prolog d’une base de connaissances en lo-gique propositionnelle, qui utilise les opérateurs ∧, ∨ et ¬. Ces opérateurs sontreprésentés par les termes fonctionnels and et or, qui prennent deux arguments,et neg, qui n’en prend qu’un seul. Un argument peut être une proposition ou uneexpression. Par exemple, supposons la base de connaissances suivante :

P

¬S ∧ Q

P ∨ ¬(T ∧ R)

Cette base serait représentée par la liste suivante :

[p, and(neg(s),q), or(p,neg(and(t,r)))]

Définissez un prédicat verif(F,I), où F est la liste qui représente la base deconnaissances et I une interprétation, c’est-à-dire la liste des propositions vraies.La résolution doit retourner avec succès si la base est vraie selon l’interprétation I.Si une proposition se trouve dans la liste I, elle est vraie, sinon elle est considéréefausse. Voici des exemples d’exécution qui montrent comment doit se comporterle programme :

?- verif([p, and(neg(s),q), or(p,neg(and(t,r)))],[p,q]).Yes?- verif([p, and(neg(s),q), or(p,neg(and(t,r)))],[p,s,q]).No

7 Interprétations déclarative et procéduraleEn Prolog, il y a deux manières d’analyser un programme. La première est une

interprétation déclarative, où les règles représentent un état du monde. Elles sont

44

Page 45: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

utilisées pour déterminer si une situation est vraie ou fausse. Le prédicat memberdéfini auparavant est perçu de manière déclarative quand on fait une requête dutype member(2, [1,2,3,4]). L’interprétation déclarative, dans ce cas, est lasuivante : un élément est membre d’une liste s’il est le premier ou s’il est membredu reste de la liste.

L’autre interprétation, dite procédurale, considère les règles comme des re-cettes pour atteindre un certain objectif. Une règle du type A :- B,C. est com-prise de la manière suivante : pour résoudre A, il faut résoudre B et ensuite C. Parexemple, le prédicat append(L1,L2,L3) suggère naturellement une interpréta-tion procédurale : pour concaténer L1 e L2, on doit d’abord concaténer la queue deL1 avec L2, ajouter le premier élément de L1 à la tête du résultat obtenu et unifierle tout avec L3.

En résumé, on a une interprétation procédurale quand on tient compte de l’al-gorithme de résolution de Prolog pour comprendre le programme. Entre autres, ondoit alors tenir compte de l’ordre d’apparition des buts durant la résolution. D’unautre côté, on a une interprétation déclarative quand on peut comprendre un pro-gramme en faisant complètement abstraction de l’algorithme de résolution.

Pour mieux comprendre ces concepts, nous allons étudier quelques exemples.Soit le programme suivant :

plus(0,X,X).plus(suc(X),Y,suc(Z)):-

plus(X,Y,Z).

Dans une interprétation déclarative, il est vu comme un programme qui déter-mine si la somme des deux premiers arguments est égale au troisième :

?- plus(suc(suc(0)),suc(suc(0)), suc(suc(suc(suc(0))))).Yes?- plus(suc(suc(0)),suc(suc(0)), suc(suc(0))).No

Dans une interprétation procédurale, il peut être utilisé pour calculer la sommede 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, ellepeut avoir plusieurs interprétations différentes. Par exemple, le prédicat plus peutête utilisé pour réaliser un addition, tout comme une soustraction :

45

Page 46: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- plus(suc(suc(0)),X , suc(suc(suc(suc(0))))).X = suc(suc(0))

Considérons maintenant le programme factoriel présenté antérieurement :

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, avecla requête factoriel(3,24) par exemple, il entrera plutôt dans une boucleinfinie (pourquoi ?). Pour résoudre ce problème, il faut ajouter un autre test dans laderniè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 derequête. Par exemple, on ne peut pas l’utiliser pour identifier le nombre x tel quex! = 24 :

?- factoriel(X,24).

ERROR: Arguments are not sufficiently instantiated^ Exception: (7) _G311 is _G239-1 ?

Le problème ici est que l’opérateur is exige qu’il n’y ait aucune variable libreà sa droite. Pour obtenir un programme qui fonctionne correctement, il faut ajou-ter une clause dans la définition et ajouter des tests pour déterminer si le premierargument est une variable libre :

46

Page 47: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

factoriel(0,1).factoriel(X,Y):-

var(X),proc_fact(0,Y,X).

factoriel(X,F):-nonvar(X),X > 0,Y is X-1,factoriel(Y,Fy),F is Fy * X.

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’à ceque l’on tombe sur une valeur qui correspond à l’entrée Y ou qu’on dépasse cettevaleur (dans ce dernier cas, il aura échec).

Pour terminer cette discussion, voyons un dernier exemple qui permet lui aussiles deux interprétations. Il s’agit du prédicat next(X,L,Y), qui réussit si Y suitimmé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 :

?- 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 variablesX et Y :

47

Page 48: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- 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 = 1Y = 2 ;

X = 2Y = 3 ;

X = 3Y = 4 ;

No

Notons que les programmes n’ont pas tous un interprétation déclarative. Unbon exemple est le prédicat liste_femmes, que nous avons présenté antérieu-rement. L’utilisation des prédicats write et fail forcent une interprétation pro-cédurale du programme.

8 Construction de nouvelles structures8.1 Appariement

Un appariement entre une structure donnée et une structure produite permetla construction de cette dernière parallèlement au parcours de la structure donnée.L’idée est la suivante : pour chaque élément de la structure donnée en entrée, onproduit un élément qui se retrouvera à la position correspondante dans la structurequi sera retournée comme résultat. On peut éventuellement avoir des cas où unitem traité dans la structure d’entrée n’a aucun correspondant dans la structure desortie. Il se peut aussi que l’item ait plusieurs valeurs qui lui correspondent dansla 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([],_,[]).

48

Page 49: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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]

Programme pour éliminer tous les nombre répétés d’une liste :

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]

Attention : voici une autre définition du prédicat eliminer_rep qui apparem-ment fonctionne bien mais qui est incorrecte si on veut éliminer uniquement lesrépétitions consécutives :

eliminer_rep([],[]).eliminer_rep([X|R],L):-

member(X,R),eliminer_rep(R,L).

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]

49

Page 50: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- eliminer_rep([1,2,2,2,3,6,2,2,2,7,7,10],L).L = [1,2,3,6,7,10]

Programme pour construire une expression arithmétique :

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

8.2 Utilisation d’un accumulateur :On utilise un accumulateur quand on ne peut pas construire une nouvelle struc-

ture parallèlement à la structure fournie en entrée, ou encore pour obtenir une ré-cursivité plus efficace. À chaque pas, on mémorise une structure intermédiaire quel’on construit graduellement. Le programme retourne la structure construite quandil arrive à la fin du processus récursif.Exemples :Exemple classique de l’inversion d’une liste :

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]

50

Page 51: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Tri par insertion :

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]

8.3 Autres exemples :Évaluation d’une expression arithmétique :

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),eval(Y,Env,Y1),apply(Op,X1,Y1,Val).

apply(+,X,Y,Val):- Val is X+Y.apply(-,X,Y,Val):- Val is X-Y.apply(*,X,Y,Val):- Val is X*Y.apply(/,X,Y,Val):- Val is X/Y.

?- eval(exp(3,*,exp(4,-,a)),[[a,2],[b,8]],X).

51

Page 52: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

X = 6

Pour évaluer une expression, le programme reçoit deux structures. Une estl’expression, qui peut contenir des constantes qui ne sont pas des nombres maisqui 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 contientl’expression.

Remarque : Dans cet exemple, on utilise les prédicat pré-définis (voir la section15, 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(X, [X|Reste], Reste).sousliste_apres(X,[_|Reste], R):-

sousliste_apres(X,Reste,R).

?- 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, nousaimerions 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]

Pour obtenir ce comportement, il faut modifier le programme, en utilisant leprédicat pré-défini \+, qui représente la négation (voir la section 12.3) :

sousliste_apres(X, [X|Reste], Reste):-\+ member(X,Reste).

sousliste_apres(X,[_|Reste], R):-sousliste_apres(X,Reste,R).

8.4 Exercices8.1 Écrivez un programme pour dupliquer les éléments d’une liste :

?- dupliquer([a,c,f],X).X = [a,a,c,c,f,f]

52

Page 53: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

8.2 Soit un programme pour calculer la somme d’une liste de nombres :

?- somme([3,7,9],X).X = 19

a) Écrivez une version récursive de ce programme qui n’utilise pas la techniquede l’accumulateur.

b) Écrivez une nouvelle version qui utilise la technique de l’accumulateur.

8.3 Écrivez un programme qui reçoit une valeur et partitionne une liste, retournantune liste qui contient toutes les items qui sont inférieurs ou égaux a cette valeur, etune 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 autreliste :

?- 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 inclusdans une autre liste :

?- 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 etvice-versa :

?- traduire([i,eat,an,orange],P).P = [je,mange,une,orange]?- traduire(E,[je,suis,ici]).E = [i,am,here]

53

Page 54: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

*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]

8.8 Écrivez une programme qui concatène une liste de listes :

?- 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.9 Écrivez un programme qui effectue un tri par sélection.

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 = []?- 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 sontde tailles différentes, on complète avec les items restants dans la liste de plus grandetaille.

?- intercaler([],[], L).L = []?- intercaler([a],[], L).L = [a]

54

Page 55: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- 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 lepré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 section9) :

?- num_app([a,b,a,d,c,c,a,d],L).L = [[c,2],[d,2],[b,1],[a,3]]

8.14 Modifiez le programme de l’exercice 3.2 pour obtenir le chemin qui permetde 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).

55

Page 56: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Chemin = [salle1, salle2, salle4]

?- sortir(salle3,Chemin).No

8.15 Transformez le programme factoriel de la section 6 en utilisant la tech-nique de l’accumulateur.

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.

9 Ordre des clausesUn aspect imporant à ne pas oublier, quand on programme en Prolog, est l’ordre

des clauses. Cet ordre détermine quelle sera la première solution retournée. Voyons,par exemple, l’utilisation de member avec une variable libre comme premier ar-gument :

member(X,[X|_]).member(X,[_|Y]):-

member(X,Y).

?- member(Z,[a,b,c]).Z = a ;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 ;

56

Page 57: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Z = b ;Z = a ;No

Dans d’autres cas, l’ordre peut faire en sorte que le processus récursif ne s’arrêtejamais :

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

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 leprédicat avec le reste de la liste et on retourne le résultat obtenu + 1. Finalement, sion n’a affaire à aucun de ces cas, on résout récursivement le prédicat et on retournela valeur obtenue.

Voyons maintenant le même programme, mais avec les deux dernières clausesinversé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),

57

Page 58: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Num is N + 1.

Si on se limite à une interprétation déclarative, les deux programmes sont équi-valents. Par contre, ce n’est pas du tout le cas, si on considère l’interprétation pro-cédurale, où l’ordre des clauses est pris en compte. On obtient tout simplement uneréponse erronée :

?- num_occ(b,[a,b,f,b,f,k,b,a],X).X = 0

La raison de ce comportement est que la seconde clause réussit toujours lorsquela liste n’est pas vide. Alors, le processus récursif se poursuit toujours avec ladeuxième clause, jusqu’à qu’on atteigne la condition d’arrêt représentée par la pre-mière clause.

Remarque : Le programme, même avec l’ordre correct des clauses, retourne denouvelles solutions erronées, si on force le retour arrière :

?- num_occ(b,[a,b,f,b],X).X = 2 ;

X = 1 ;

X = 1 ;

X = 0 ;

No

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 sonttoujours indiqués en italique) :

58

Page 59: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

N = Num2

Num = Num1

X = Num

Num2 = Num3

num_occ(b,[a,b,f,b],X)

num_occ(b,[b,f,b],Num)

num_occ(b,[b],Num2)

num_occ(b,[],0)

num_occ(b,[f,b],N) Num1 is N+1

num_occ(b,[],N1) Num3 is N1+1

N1 = 0

===> X = 2

Pour fournir la seconde solution, l’interpréteur Prolog recule au dernier pointde choix : le but num_occ(b,[b],Num2), qui peut être unifié avec la dernièreclause. La suite de la résolution nous amène donc au résultat suivant :

X = Num

Num = Num1

N = Num2

Num2 = Num3

num_occ(b,[b],Num2)

num_occ(b,[],0)

Num3 = 0

num_occ(b,[b,f,b],Num)

num_occ(b,[f,b],N) Num1 is N+1

num_occ(b,[a,b,f,b],X) ===> X = 1

num_occ(b,[],Num3)

59

Page 60: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Pour obtenir la troisième solution, on recule au dernier point de choix, soit le butnum_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 unnouveau point de choix :

X = Num

Num = Num1

Num1 = Num2

Num2 = Num3

num_occ(b,[b],Num2)

num_occ(b,[],0)

N = 0

num_occ(b,[b,f,b],Num)

num_occ(b,[a,b,f,b],X) ===> X = 1

num_occ(b,[],N) Num3 is N + 1

num_occ(b,[f,b],Num1)

Finalement, si on reprend la résolution à partir du dernier point de choix qui s’estajouté, on obtient la dernière solution :

60

Page 61: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

X = Num

Num = Num1

Num1 = Num2

Num2 = Num3

num_occ(b,[b],Num2)

num_occ(b,[],0)

Num3 = 0

num_occ(b,[b,f,b],Num)

num_occ(b,[a,b,f,b],X) ===> X = 0

num_occ(b,[],Num3)

num_occ(b,[f,b],Num1)

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 laseconde version du programme eliminer_rep, que nous avons vu à la section8.1. Rappelons-nous que ce programme élimine les répétitions dans un liste :

eliminer_rep([],[]).eliminer_rep([X|R],L):-

member(X,R),

61

Page 62: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

eliminer_rep(R,L).eliminer_rep([X|R],[X|L]):-

eliminer_rep(R,L).

Avant d’étudier les détails de l’exécution de ce programme, nous allons d’abordconsidérer une version où les deux buts de la deuxième clause sont inversés :

eliminer_rep([],[]).eliminer_rep([X|R],L):-

eliminer_rep(R,L),member(X,L).

eliminer_rep([X|R],[X|L]):-eliminer_rep(R,L).

Soit la requête suivante :

?- eliminer_rep([1,2,3,2],Z).Z = [1,3,2]

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)

member(1,L)eliminer_rep([2,3,2],L)

eliminer_rep([3,2],L1)

eliminer_rep([2],L2)

eliminer_rep([],L3) member(2,L3)

member(2,L1)

member(3,L2)

eliminer_rep([],[])

L3 = []

ECHEC

Z = L

L = L1

L1 = L2

L2 = L3

Il tentera alors de retourner au dernier point de choix, c’est-à-dire le but eliminer_rep([2],L2)qu’il reussira à unifier avec la dernière clause. La suite de la résolution entraînera

62

Page 63: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

à nouveau un échec :

eliminer_rep([1,2,3,2],Z)

member(1,L)eliminer_rep([2,3,2],L)

eliminer_rep([3,2],L1) member(2,L1)

member(3,L2)

Z = L

L = L1

L1 = L2

eliminer_rep([],L3)

eliminer_rep([],[])

L3 = []

L2 = [2|L3]

ECHEC

eliminer_rep([2],L2)

On recule donc au dernier point de choix, soit le but eliminer_rep([3,2],L1).Encore une fois, on se butera à un échec :

63

Page 64: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

eliminer_rep([1,2,3,2],Z)

member(1,L)eliminer_rep([2,3,2],L)

member(2,L1)

Z = L

L = L1

eliminer_rep([3,2],L1)

L1 = [3|L2]

eliminer_rep([2],L2)

eliminer_rep([],L3)

L2 = L3

member(2,L3)

eliminer_rep([],[])

L3 = []

ECHEC

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 :

64

Page 65: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

eliminer_rep([1,2,3,2],Z)

eliminer_rep([2,3,2],L)

Z = L

L = L1

eliminer_rep([3,2],L1)

L1 = [3|L2]

eliminer_rep([2],L2)

eliminer_rep([],L3)

L2 = [2|L3]

L3 = []

eliminer_rep([],[])

member(1,L)

member(2,L1) ECHEC

member(2,[3,2])

Cette fois, on reculera au but eliminer_rep([2,3,2],L), que l’on unifieraavec la dernière clause. Avec encore trois retours arrière, on finira par obtenir lasolution L = [2,3,2] pour ce but. Mais on aura encore un échec à la résolutiondu but suivant, member(1,[2,3,2]). Alors, pour obtenir la solution finale, ondevra retourner au dernier point de choix disponible. Pour obtenir la solution finaleà partir de là, beaucoup de retours arrière seront encore nécessaires.

Comparons maintenant avec l’autre version (bien meilleure !) du même pro-gramme :

eliminer_rep([],[]).eliminer_rep([X|R],L):-

member(X,R),eliminer_rep(R,L).

eliminer_rep([X|R],[X|L]):-eliminer_rep(R,L).

Voici l’état de la résolution au moment du premier échec :

65

Page 66: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

eliminer_rep([1,2,3,2],Z)

eliminer_rep([2,3,2],L)member(1,[2,3,2])

Z = L

member(1,[3,2])

member(1,[2])

member(1,[])

ECHEC

On remarquera qu’il ne sera pas nécessaire de tenter de résoudre le buteliminer_rep([2,3,2],L), puisque nous savons déjà que member(1,[2,3,2])a échoué. On évite ainsi tous les retours arrières qui ont été effectués avec l’autreversion du programme.

Ainsi, l’interprète Prolog sait déjà qu’il doit tenter de nouveau à partir du pointchoix eliminer_rep([1,2,3,2],Z). En unifiant avec la seconde clause, lasuite de la résolution nous amène à un nouvel échec :

66

Page 67: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Z = [1|L]

member(2,[3,2])

L = L1

eliminer_rep([2],L2)

L1 = L2

eliminer_rep([2,3,2],L)

eliminer_rep([3,2],L1)

eliminer_rep([1,2,3,2],Z)

OK

ECHEC

member(3,[2])

Maintenant, la résolution reprend au point de choix eliminer_rep([3,2],L1),et tombe sur un nouvel échec :

67

Page 68: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Z = [1|L]

member(2,[3,2])

L = L1

eliminer_rep([2,3,2],L)

eliminer_rep([1,2,3,2],Z)

OK

eliminer_rep([3,2],L1)

L1 = [3|L2]

member(2,[])

ECHEC

eliminer_rep([],L3)

L2 = L3

eliminer_rep([2],L2)

Le prochain point de choix considéré est le but eliminer_rep([2],L2).Cette fois, la résolution se termine avec succès :

68

Page 69: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Z = [1|L]

member(2,[3,2])

L = L1

eliminer_rep([2,3,2],L)

eliminer_rep([1,2,3,2],Z)

OK

eliminer_rep([3,2],L1)

L1 = [3|L2]

eliminer_rep([2],L2)

L2 = [2|L3]

eliminer_rep([],L3)

eliminer_rep([],[])

L = []

===> Z = [1,3,2]

Remarquez qu’il reste un point de choix. Par conséquent, si nous demandons unenouvelle solution, l’interpréteur reculera à ce point de choix et retournera une nou-velle solution (erronée).

10 Manipulation dynamique de programmesEn Prolog, il est possible qu’un programme soit modifié dynamiquement. Plus

précisément, un programme peut se modifier lui-même en ajoutant ou en retirantdes clauses. Il n’est pas facile de fournir un exemple simple de programme qui semodifie lui-même, mais nous verrons les prédicats qui permettent ce procédé :assert, qui ajoute une nouvelle clause, et retract, que retire la premièreclause s’unifiant avec l’argument.

Supposons un programme pour calculer la somme d’une liste de produits :

prix(pomme, 0.50).prix(riz, 1.40).prix(pain, 2.70).

69

Page 70: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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, nousexé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’enajouter 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 demanipuler aussi des clauses. Voyez par exemple comment nous pourrions modifierle programme pour que la prix des oranges soit toujours identique au prix despommes :

?- assert(prix(orange,P):- prix(pomme,P)).Yes

Remarque : Par défaut, SWI-Prolog ne permet pas d’utiliser assert et retractpour des prédicats qui sont déjà définis dans un programme. Pour pouvoir le faire, ilfaut que ces prédicats soient déclarés dynamiques, au début du programme. Pour cefaire, on utilise l’expression :-dynamic suivie de tous les prédicats dynamiquesavec leur arité (c’est-à-dire leur nombre d’arguments). Dans notre exemple, nousavons un prédicat dynamique d’arité 2 :

:- dynamic prix/2.

70

Page 71: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

11 Recupération de solutions multiples11.1 Utilisation du retour arrière

Pour obtenir toutes les solutions d’une résolution, la seule manière que nousconnaissons jusqu’à maintenant est l’utilisation répétitive du point-virgule aprèschaque solution retournée. Soit le programme suivant :

homme(roberto).homme(paulo).homme(joao).homme(armando).femme(lucia).femme(clara).pere_de(roberto,paulo).pere_de(joao,paulo).pere_de(lucia,armando).pere_de(clara,armando).

Pour identifier tous les enfants de Paulo, nous utiliserions la méthode suivante :

?- pere_de(X,paulo).X = roberto ;X = joao ;No

Mais cela ne fonctionne pas si nous désirons récupérer toutes les solutions pouren faire un traitement. Par exemple, si nous voulons calculer le nombre d’enfantsque Paulo a, il nous faut une manière de mettre tous les enfants dans une liste etcalculer le nombre d’items que cette liste contient. On pourrait toujours résoudrele problème en utilisant des prédicats dynamiques :

tous_les_enfants(X,L):-assert_enfants(X),retract_enfants(L).

assert_enfants(X):-pere_de(F,X),assert(enfant(F)),fail.

assert_enfants(_).

71

Page 72: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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, ilajoute un fait pour chaque enfant de X. Remarquez l’usage de fail, pour forcer leretour arrière, afin d’identifer tous les enfants de X (le prédicat assert n’a jamaisde point de choix). À la seconde étape, il retire tous les faits qui ont été ajoutés, etles met dans une liste.

11.2 Prédicat findallLe problème avec la solution précédente est qu’elle est complexe. Pour ré-

cupérer la même information, il est beaucoup plus simple d’utiliser le prédicatfindall. Ainsi, pour obtenir la liste de tous les enfants de Paulo, nous exécute-rions la requête suivante :

?- findall(X,pere_de(X,paulo),L).L = [roberto,joao]

Le fonctionnement est le suivant. L’interprète Prolog tente de toutes les ma-nières possibles de résoudre le but qui se retrouve comme second argument defindall. Plus précisément, il cherchera une première solution, puis fera des re-tours arrière jusqu’à ce qu’il n’y ait plus de solution. Pour chaque tentative réussie,il ajoute dans une liste le résultat des instanciations du premier argument. Finale-ment, le dernier argument, qui doit toujours être une variable, sera unifié avec laliste de solutions. L’ordre des solutions dans la liste corresondra à l’ordre des solu-tions trouvées lors de la résolution. S’il n’y a aucune solution, on obtiendra la listevide. Pour revenir à notre exemple, voici le programme pour calculer le nombred’enfants :

nombre_enfants(Personne,Num):-findall(F,pere_de(F,Personne),Enfants),length(Enfants,Num).

?- nombre_enfants(paulo,N).N = 2?- nombre_enfants(joao,N).N = 0

72

Page 73: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 sonttoutes les personnes impliquées dans une relation père-enfant. Dans ce cas, on peututiliser findall, en utilisant une paire de variables comme premier argument :

?- findall([X,Y],pere_de(X,Y),L).L = [[roberto,paulo],[joao,paulo],

[lucia,armando],[clara,armando]]

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écuterionsla requête suivante :

?- findall(X,(pere_de(F,X),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_filledéfini de la manière suivante :

a_une_fille(Personne):-findall(X,pere_de(X,Personne),Enfants),findall(X,(member(X,Enfants),femme(X)),Filles),Filles \= [].

?- a_une_fille(paulo)No?- a_une_fille(armando)Yes

Ce programme fonctionne ainsi : il extrait la liste de tous les enfants, puisexrait de cette liste tous ceux qui sont des femmes, pour finalement vérifier quecette dernière liste n’est pas vide. Le problème avec ce programme est qu’il nefonctionnera pas correctement si on l’appelle avec une variable libre. Il retourneraavec succès, mais sans instancier la variable :

?- a_une_fille(X)X = _G109

73

Page 74: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 deuxpremiers arguments du prédicat demeurent libres au retour de la résolution. Poursolutionner notre problème, nous pouvons modifier le programme, en ajoutant unprédicat qui vérifie si X est un hommme :

a_une_fille(Personne):-homme(Personne),findall(X,pere_de(X,Personne),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 unefille :

?- findall(X,a_une_fille(X),L).L = [armando]

Nous verrons, plus loin, un mécanisme de contrôle, le coupe-choix, qui offreune solution plus directe pour éliminer les solutions redondantes.

11.3 Prédicats bagof et setofTout comme le prédicat findall, les prédiats bagof et setof retournent

toutes les instanciations d’un terme telles que les instanciations des variables qu’ilcontient satisfont aussi le but passé en second argumant. Deux choses distinguentces prédicat de findall :

– S’il n’y a aucune manière de trouver une telle instanciation, le prédicatéchoue, au lieu de retourner la liste vide.

– Si le but contient des variables qui ne se retrouvent pas dans le premier terme,on ne retourne que les solutions liées à une instanciation spécifique de cesvariables.

Ce qui distingue setof de bagof est que le premier élimine les solutionsredondantes.

Voici un exemple qui illustre l’usage de bagof :

74

Page 75: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 = _G173X = 1L = [a, b] ;

Y = _G173X = 2L = [c, d] ;No

Revenons maintenant au prédicat a_unde_fille que nous avons défini au-paravant en utilisant findall :

a_une_fille(Personne):-findall(X,pere_de(X,Personne),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 pasd’enfant, ou si elle a des enfants mais qu’aucun n’est une fille. Ceci suggère unemeilleure solution qui utlise le prédicat bagof :

a_une_fille3(Personne):-bagof(X,pere_de(X,Personne),femme(X),_).

Remarquez que ce prédicat échoue si la personne n’a pas de fille. Si elle a unefille, la résolution se termine avec succès, et on ne s’interesse pas à la liste retournéepar le prédicat bagof.

75

Page 76: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

11.4 Exercices11.1 Nous voulons utiliser le prédicat sortir de l’exercice 3.2 pour écrire unprogramme 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).

Identifiez le problème et écrivez une version qui fonctionne.

11.2 En utilisant le prédicat findall, écrivez un programme qui reçoit deux listeset 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 = []

11.3 Que fait le programme suivant ?

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.

11.4 Utilisez assert et retract pour définir le prédicat nombre_enfants.

12 Mécanismes de contrôle12.1 Coupe-choix :

Le coupe-choix (cut) est le prédicat " !", qui est utilisé pour couper l’arbre derecherche quand un retour arrière est effectué. Principalement, on l’utilise pourempêcher qu’un programme ne tente de retourner une nouvelle solution. L’effet ducoupe-choix est le suivant :

76

Page 77: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

Quand l’interpréteur Prolog rencontre un coupe-choix, il oublietous les points de choix qui ont été ajoutés lors de l’unification de latête de la clause et le but résolu, ainsi que lors de la résolution de tousles 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 oneffectue un retour arrière :

?- somme_jusque(3,X).X = 6 ;boucle infinie...

Ceci se produit parce qu’au moment de la résolution du but somme_jusque(1,S1),l’unification s’est faite avec la première clause, ce qui a entraîné la mémorisationd’un point de choix, puisqu’il est aussi possible d’unifier avec la seconde clause.Lors du retour arrière, l’interpréteur retournera à ce point de choix et poursuivra larésolution avec la seconde clause, ce qui causera la boucle infinie. Pour éviter ceproblème, il suffit d’ajouter un coupe-choix à la première clause :

somme_jusque(1,1):-!.somme_jusque(N,S):-

M is N-1,somme_jusque(M,P),S is P + N.

Ainsi, quand nous demanderons un retour arrière, l’interpréteur ne tentera pasl’unification avec la seconde clause.

Un autre exemple est un programme qui extrait d’une liste de personnes cellesqui sont de jeunes pères :

pere_de(ana,roberto).pere_de(romeu,paulo).

77

Page 78: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

pere_de(tomas,michel).pere_de(francisco,roberto).age(roberto,23).age(michel,35).age(paulo,53).age(tomas,7).

peres_jeunes([],[]).peres_jeunes([X|Reste],[X|R]):-

pere_de(_,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

La situation qui prévaut quand l’interprète Prolog retourne la première solutionest telle qu’illustré à la figure 11 (les buts qui possèdent un point de choix sontindiqués en italique).

Le premier point de choix qui sera considéré, lors du retour arrière, est le butparents_jeunes([michel],R), qui pourra être réunifié avec la secondeclause. La poursuite de la résolution à partir de ce point amènera l’ajout du butperes_jeunes([],R) dans la résolvante, sans ajouter la constante micheldans la liste de résultats.

Au second retour arrière, on recule au but pere_de(_,roberto), qui réus-sira à nouveau, en unifiant avec le fait pere_de(francisco,roberto). Onobtiendra à nouveau la première solution, avec encore une fois un point de choixau but peres_jeunes([michel],R).

78

Page 79: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

age(roberto,23)

35<36age(michel,35)

R2=[ ]

peres_jeunes([ ],[ ])

peres_jeunes([ ],R2)

peres_jeunes([roberto,paulo,michel],X)

peres_jeunes([michel],R1)

23<36 peres_jeunes([paulo,michel],R)

X = [roberto | R]

R=[michel | R2]

pere_de(_,roberto)

pere_de(ana,roberto)

pere_de(_,michel)

R = R1

FIG. 11 – Arbre de résolution

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 nouvellesolution : [michel]. Pour arriver à cette solution, un nouveau point de choix aété ajouté, comme l’illustre la figure 12.

Lors du dernier retour arrière, on repartira donc de ce dernier point de choixpour redémarrer la résolution, ce qui amènera à unifier la variable R avec la listevide.

La solution pour éviter toutes ces réponses erronées est l’utilisation du coupe-choix avant l’appel récursif de la seconde clause (on peut aussi le mettre aprèsle dernier but de la clause et obtenir le même effet). Ceci signifie que quand ontrouvera un jeune père, il ne sera jamais possible de rechercher d’autres solutionspour ce cas :

peres_jeunes([],[]).peres_jeunes([X|Reste],[X|R]):-

pere_de(_,X),age(X,I),I < 36,!,

79

Page 80: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

peres_jeunes([ ],R2)

peres_jeunes([ ],[ ])

peres_jeunes([roberto,paulo,michel],X)

peres_jeunes([paulo,michel],X)

peres_jeunes([michel],R)

age(michel,35) 35<36

R=[michel | R2]

R2=[ ]

pere_de(_,michel)

FIG. 12 – Arbre de résolution

80

Page 81: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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_de(_,X)dans la seconde clause. Le coupe-choix qui a été ajouté résout une partie du pro-blème. Mais le programme est encore inefficace. Si on a un père qui a plus d’un filset dont l’âge est supérieur à 36 ans, on résoudra plusieurs fois le but pere_de(_,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_de(_,X)(pourquoi ?). Pour résoudre le problème, il faut plutôt définir un nouveau prédicatest_un_pere et mettre le coupe-choix dans la définition de ce prédicat. Le nou-veau 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_de(_,X),!.

Pour éliminer le point de choix qui apparaît au moment de l’unification avec laseconde clause sans utiliser de coupe-choix, on peut modifier le programme de lamanière suivante :

pere_jeune(X):-pere_de(_,X),age(X,I),I < 36.

peres_jeunes([],[]).peres_jeunes([X|Reste],[X|R]):-

pere_jeune(X),peres_jeunes(Reste,R).

81

Page 82: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

peres_jeunes([X|Reste],R):-\+ pere_jeune(X),peres_jeunes(Reste,R).

Malheureusement, cette solution n’est pas optimale. Nous avons éliminés lesréponses erronées, mais pas les réponses redondantes, qui sont dues au fait qu’unpère peut avoir plusieurs enfants. En d’autres mots, on a toujours un point de choix,au but pere_de(_,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ésultantene soit pas vide. Cela fonctionne parce que le prédicat findall n’ajoute jamaisde point de choix :

pere_jeune(X):-findall(F,pere_de(F,X),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).

Nous avons finalement obtenu une version du programme sans coupe-choix,mais il s’agit d’un programme qui est moins efficace (à cause de l’utilisation duprédicat findall). En effet, il est inutile d’identifier la liste de tous les fils. Ilsuffit de vérifier qu’il en existe un.

On voit qu’il a fallu modifier sensiblement le programme pour éviter l’utilisa-tion du coupe-choix, et le résultat n’est pas un programme plus facile à comprendre.En plus, le prédicat pere_jeune est appelé une deuxième fois, soit dans la troi-sième clause de la définition de peres_jeunes, si la résolution échoue avec ladeuxième clause. En conclusion, il est préférable d’utiliser le coupe-choix dans cetexemple. D’autant plus qu’il ne modifie en rien la sémantique du programme. Il nefait qu’éliminer des solutions erronées et/ou redondantes.

Il est très important de comprendre que l’effet du coupe-choix est d’éliminertous les points de choix qui ont été ajoutées depuis le moment où la tête de la clausea été unifiée. Pour mieux comprendre, considérons le programme suivant :

82

Page 83: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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(1).No

La figure 13 illustre l’arbre de résolution au moment où l’on arrive au coupe-choix. À ce moment-là, il y a trois points de choix. Une fois le coupe-choix traité,le point de choix associé à son noeud parent et tous les points de choix dans lessous arbres à sa gauche sont éliminés. Ceci signifie que les points de choix r(1)et q(1) seront éliminés, mais pas le point de choix pred(1).

Après le traitement du coupe-choix, la résolution du but c(1) échoue. Oneffectue alors un retour arrière à l’unique point de choix qui subsiste. La résolutiondémarre à partir de ce point, en tentant d’unifier le but avec la seconde clause duprédicat pred. Ceci mènera à un nouvel échec, puisqu’il ne sera pas possible derésoudre le but d(1).

Considérons maintenant une version un peu différente du programme, où lecoupe-choix apparaît dans la définition du prédicat pred :

p(1).p(2).d(2).f(1).

83

Page 84: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

f(1)

c(1)!

pred(1)

q(1)

r(1)

p(1)

OK

FIG. 13 – Exemple d’utilisation de coupe-choix

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

84

Page 85: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

c(2)

p(2)

OK

! f(2)

OK

pred(2)

q(2)

r(2)

FIG. 14 – Exemple d’utilisation de coupe-choix

12.2 Dangers avec le coupe-choixLe coupe-choix est un outil qui peut s’avérer dangereux, spécialement quand

on développe un programme en ayant en tête une interprétation procédurale. Ledanger est d’obtenir un programme dont la sémantique diffère de celle que nousvoulions obtenir. Plus précisément, le programme peut ne pas fonctionner danscertaines situations qui n’ont pas été considérées lors du développement.

Considérons, par exemple, un programme pour identifier le plus petit de deuxnombres :

min(X,Y,X):-X =< Y, !.

min(X,Y,Y).

L’auteur de ce programme a réalisé que si la résolution réussit avec la premièreclause, il faut mettre un coupe-choix pour éviter une seconde réponse erronée encas de retour arrière. Si la résolution échoue avec la première clause, cela signifieque Y est plus petit que X. Il n’est donc pas nécessaire d’ajouter un test dans laseconde clause :

?- min(2,3,X).X = 2 ;No

?- min(5,2,X).

85

Page 86: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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 programmeurn’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 nombredonné 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 auraitdu é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 importantde remarquer que ce problème ne se serait pas produit si le programmeur avaitutilisé 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 secondeclause qui, de toute façon, entraînerait un échec lors de la résolution du but qui setrouve 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.

86

Page 87: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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

Si on exécute la requête etudiant_ancien(X), nous n’obtenons aucunesolution, malgré le fait qu’il en existe une (X = jorge). Remarquez qu’en interchan-geant l’ordre des deux buts dans le corps de la clause, nous obtiendrions la réponsecorrecte.

12.4 Exercices12.1 En utilisant le coupe-choix, modifiez le programme de l’exercice 8.14 pouréliminer les solutions multiples.

12.2 Soit le programme suivant :

87

Page 88: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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

Et quel sera le résultat si on demande une seconde solution ?

12.3 Soit le programme suivant :

m(a).m(c).q(a).r(a).r(b).r(c).

p(X):-q(X),!,fail.

88

Page 89: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

p(X):-r(X).

s(X):-m(X),p(X).

a) Quel sera le résultat de la requête suivante :

?- s(X).

b) Quel sera le résultat si on demande une seconde solution ?

12.4 Pourquoi l’ajout d’un coupe-choix après le but pere_de(_,X) ne résoutpas le problème d’efficacité dans la définition de peres_jeunes?

13 Exemples additionnels13.1 Tours de Hanoi

Les tours de Hanoi est un jeu où n rondelles de tailles décroissantes sont empi-lées autour d’une tige. Le but est de déplacer cette pile autour d’une autre tige, ens’aidant d’une troisième tige. Les règles sont les suivantes :

– On ne peut déplacer qu’une rondelle à la fois.– On ne peut pas déposer une rondelle sur une autre de plus petite taille.

La 15 illustre une solution pour une tour de trois rondelles.

Le principe récursif pour résoudre le problème d’une tour de n rondelles est lesuivant. On résout le problème plus simple qui consiste à déplacer toutes les n− 1premières rondelles de la pile à une autre tige. On déplace la rondelle restante àla troisième tige, puis on rappelle à nouveau le processus récursif pour déplacer lapile de n− 1 rondelles sur la rondelle qu’on vient de déplacer. Voici le programmeProlog qui applique cette méthode :

hanoi(N):-deplacer(N,gauche,centre,droite).

deplacer(1,_,_,_):-!.

89

Page 90: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

FIG. 15 – Tours de Hanoi

deplacer(N,A,B,C):-M is N-1,deplacer(M,A,C,B),write(A),write(’--->’),write(B),nl,deplacer(M,C,B,A).

13.2 QuicksortVoici le programme qui implémente le quicksort, en prenant le premier élément

de la liste comme pivot.

quicksort([],[]).quicksort([X|Xs],Ys):-

partition(Xs,X,Part1,Part2),quicksort(Part1,Part1triee),quicksort(Part2,Part2triee),append(Part1triee,[X|Part2triee],Ys).

partition([],_,[],[]).partition([X|Xs],Y,[X|Part1],Part2):-

X =< Y,

90

Page 91: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

partition(Xs,Y,Part1,Part2).partition([X|Xs],Y,Part1,[X|Part2]):-

X > Y,partition(Xs,Y,Part1,Part2).

13.3 Exercices13.1 Écrivez une version du programme hanoi(N) qui retourne une liste de tousles déplacements effectués.

?- hanoi(3,M).L = [deplacer(gauche,centre),deplacer(gauche,droite),...]

14 Listes de différenceNous allons maintenant voir une manière de représenter une liste comme si

nous avions un pointeur à la fin de la liste. Pour ce faire, on représente une listecomme une paire L1-L2, où L1 est une liste dont la continuation est L2. Parexemple, la liste [1,2,3] serait représentée de la manière suivante : [1,2,3|X]-X.Suivant cette logique, il faudrait représenter la liste vide par la paire X-X, c’est-à-dire une liste qui n’a pas d’éléments mais seulement une continuation possible.

Avec cette représentation des listes, on peut écrire une version efficace du pré-dicat append (renommé append_dl) :

append_dl(X-Xs,Xs-Ys,X-Ys).

?- append_dl([1,2,3|X]-X, [4,5|Y]-Y, Res).

X = [4, 5|_G408]Y = _G408Res = [1, 2, 3, 4, 5|_G408]-_G408

Il n’est pas très compliqué d’écrire un programme qui convertit une liste ordinaireen une liste de différence, et vice versa : :

convertir([],X-X):-var(X).

convertir([X|Xs],[X|Xconv]-D):-convertir(Xs,Xconv-D).

91

Page 92: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- convertir([1,2,3],Z).Z = [1, 2, 3|_G359]-_G359Yes

?- convertir(Z,[1,2,3|X]-X).Z = [1, 2, 3]X = _G315Yes

Le test var(X) est nécessaire pour le cas de la liste vide, si nous voulonspouvoir convertir une liste de différence en une liste normale.

Voyons maintenant une version efficace du prédicat inverser qui, au lieud’utiliser un accumulateur, utilise une liste de différence :

inverser(X-X,Z-Z):-var(X).

inverser([X|Xs]-Dx, Rs-Dr):-inverser(Xs-Dx, Rs-[X|Dr]).

Remarquez qu’on doit encore une fois utiliser le test var(X) pour détecter laliste vide. Dans ce cas, le programme retourne un nouvelle liste vide : Z-Z.

Tout programme utilisant le prédicat append fonctionnera plus efficacementsi on utilise des listes de différence. Voyez par exemple le programme quicksort dela section 13.2 :

quicksort_dl(X-X,Z-Z):-var(X).

quicksort_dl([X|Xs]-Dx,Ys):-partition(Xs-Dx,X,Part1,Part2),quicksort_dl(Part1,Part1triee),quicksort_dl(Part2,Part2triee-D),append_dl(Part1triee,[X|Part2triee]-D,Ys).

partition(X-X,_,Y-Y,Z-Z):-var(X),var(Y).

partition([X|Xs]-Dx,Y,[X|Part1]-D,Part2):-X =< Y,partition(Xs-Dx,Y,Part1-D,Part2).

partition([X|Xs]-Dx,Y,Part1,[X|Part2]-D):-

92

Page 93: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

X > Y,partition(Xs-Dx,Y,Part1,Part2-D).

Pour cacher la représentation de liste, on peut définir des prédicats head ettail qui retournent, respectivement, le premier élément et le reste de la liste (ànoter que head doit échouer avec une liste vide) :

head(X-X,_):-var(X),!,fail.

head([X|_]-_,X).

tail(X-X,X-X):-var(X).

tail([_|T]-D,T-D).

Il nous faudra aussi un test empty_list, pour créer une liste vide, ou testersi une liste est vide :

empty_list(X-X):-var(X).

Finalement, il nous faut un prédicat cons, qui ajoute un élément au début dela liste :

cons(X,L-D,[X|L]-D).

À l’aide de ces prédicats, on peut implémenter le quicksort avec un programmeplus lisible :

quicksort_dl(X,Z):-empty_list(X),empty_list(Z).

quicksort_dl(Xs,Ys):-head(Xs,HeadXs),tail(Xs,TailXs),partition(TailXs,HeadXs,Part1,Part2),quicksort_dl(Part1,Part1triee),quicksort_dl(Part2,Part2triee),cons(HeadXs,Part2triee,M2),append_dl(Part1triee,M2,Ys).

93

Page 94: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

partition(X,_,Y,Z):-empty_list(X),empty_list(Y),empty_list(Z).

partition(Xs,Y,M,Part2):-head(Xs,HeadXs),tail(Xs,TailXs),HeadXs =< Y,partition(TailXs,Y,Part1,Part2),cons(HeadXs,Part1,M).

partition(Xs,Y,Part1,M):-head(Xs,HeadXs),tail(Xs,TailXs),HeadXs > Y,partition(TailXs,Y,Part1,Part2),cons(HeadXs,Part2,M).

Il faudra utiliser le prédicat convertir pour pouvoir utiliser ce programme :

quicksort(Liste,Listetriee):-convertir(Liste,L),quicksort_dl(L,Z),convertir(Listetriee,Z).

14.1 Exercices14.1 Que se produit-il si on retire le but var(X) dans la première clause de

la définition de convertir? (essayez de résoudre une conversion d’une liste dedifférence en une liste normale)

14.2 En utilisant une liste de différence, redéfinissez le prédicar aplatir del’exercice 8.12.

15 Quelques prédicats prédéfinisconsult(+File)

Ouvre un fichier File et interprète les clauses qu’il contient.

94

Page 95: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

15.1 Vérification du type d’un termevar(+Terme)

Réussit si Terme est une variable libre (non instanciée).

?- var(X).Yes?- var(23).No?- X=Y, Y=23, var(X).No

nonvar(+Terme)

Réussit si Terme est une variable instanciée.Exemple : Définition d’un prédicat plus(X,Y,Z), qui peut aussi être utilisé pour

faire la soustraction :

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)

Réussit si Terme est un nombre entier.

95

Page 96: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

float(+Terme)

Réussit si Terme est un nombre réel.

number(+Terme)

Réussit si Terme est un nombre.

atom(+Terme)

Réussit si Terme est une constante qui n’est pas un nombre.

?- atom(23).No?- atom(rodrigo).Yes?- 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

96

Page 97: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

15.2 Comparaison et unification de termes+Terme1 = +Terme2

Réussit si les deux termes peuvent être unifiés.

+Termo1 \= +Termo2

Réussit si les deux termes ne peuvent pas être unifiés.

16 Conseils pour bien programmer en Prolog16.1 Erreurs fréquentes

Voici quelques erreurs fréquente que l’on rencontre dans des programmes Pro-log :

– Fautes de frappes, dans les noms de prédicat, par exemple. Le programmecompile, 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 pasde clause pour la liste vide.

16.2 Trace d’exécutionVoici un exemple de programme erroné :

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

97

Page 98: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

mystere2([],[]).mystere2([X|Reste],[X|ResteFiltre]):-

filtre(X),mystere2(Reste,ResteFiltre).

filtre(X):-W is X mod 2,W = 0.

La requête suivante devrait retourner un résultat, mais elle échoue :

?- pred1([5,4,-3],X).No

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 alorsla résolution pas à pas. À chaque pas, il montre le prochain but à résoudre, de lamanière suivante :

Call : (niveau de profondeur dans l’arbre de résolution)ButIl attend alors une action de notre part. Si on appuie sur la touche <enter>, il tentel’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) ButLe 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 programmeci-dessus :

?- trace.

Yes

98

Page 99: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

?- 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) ?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 butmystere1([-3], _G307). Ce but peut s’unifier avec la tête de la dernièreclause du prédicat mystere1, retournant plus d’un résultat, mais la nouvelle

99

Page 100: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

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, onpeut demander qu’il soit résolu sans fournir les détails de la résolution, en entrantla commande s (skip), au lieu d’appuyer sur la touche <enter>. Voici une trace dumême programme, mais sans les détails des première tentatives de résolution desbuts 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) ? sExit: ( 9) mystere1([5, 4, -3], [5, 4]) ?Call: ( 9) mystere2([5, 4], _G200) ? sFail: ( 9) mystere2([5, 4], _G200) ?Redo: ( 11) mystere1([-3], _G313) ?Call: ( 12) mystere1([], _G316) ?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) ?

No

Pour bien comprendre cette trace, comparez-la à l’arbre de résolution. La si-tuation au moment du premier échec est illustrée à la figure 16 (point de choix enitalique).

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èteeffectue un retour arrière à ce point de choix, et ajoute le but mystere1([],Z3) dans la résolvante. On obtiendra alors la liste Z3, tel qu’illustré à la figure 17.

Remarquez qu’il n’y a plus de point de choix. La résolution du second but de laclause pred sera tentée de nouveau. Le but à résoudre aura la forme mystere2([5,4,-3],X),qui n’aura pas plus de succès.

100

Page 101: Programmation logique - unilim.fr · 1 Introduction à la programmation logique 3 ... foncteur) et des arguments qui sont eux-mŒmes des termes, ... seulement un argument, ...

filtre(5)

mystere2(Z,X)

mystere2([4],ResteFiltre)

ECHEC

X = [5|ResteFiltre]

pred([5,4,−3],X)

mystere1([5,4,−3],Z)

−3 < 0 mystere1([],Z2)

Z = [5|Z1]

mystere1([4,−3],Z1)

Z1 = [4|Z2]

mystere1([],[])

Z = []

mystere1([−3],Z2)

FIG. 16 – Arbre de résolution

pred([5,4,−3],X)

mystere1([5,4,−3],Z)

Z = [5|Z1]

mystere1([4,−3],Z1)

Z1 = [4|Z2]

mystere1([−3],Z2)

Z2 = [4|Z3]

mystere1([],Z3)

mystere1([],[])

Z3 = []

mystere2(Z,X)

FIG. 17 – Arbre de résolution

101