Syntaxe et structure de des données
Une syntaxe très simple
(notation BNF – Backus-Naur Form -) qui repose sur la notion de terme
(servant de base pour décrire les données et les programmes).
< fait > ::= <
terme > .
< règle > ::= < terme > :- < termes > .
< requête > ::= < termes > .
< terme > ::= < nombre > | < atome > | < variable > |
< atome > ( < termes >)
< termes > ::= < terme > | < terme > , < termes >
avec
::= peut être
| ou
< > pour les symboles non terminaux
• Un terme est donc soit
une constante (qui se subdivise en nombre ou atome), soit une variable soit une
structure (ou terme composé).
• A l’aide des faits et
des règles, on crée donc des programmes en Prolog qui est une séquence (donc
l’ordre a de l’importance) de clauses. Une clause s’est :
Une version brève …
< fait > ::=
foncteur(arg1, arg2, …, argn) .
< règle > ::= tête :- corps .
< requête > ::= ?-fait
• Il est assez simple de
faire tourner en rond l’interpréteur Prolog. Par exemple avec :
p :- p.
?- p.
Un peu plus subtil ; en
principe on peut appeler une règle de plusieurs façons (e.g, frere(jean,V) ou
frere(B,anne)) mais il arrive que l’interpréteur réponde que sous une seule
forme et que pour l’autre il tourne en rond.
1. Constantes, variables, structures
• Prolog possède des
constantes qui se subdivisent en atomes et nombres. Les atomes comprennent les
noms (roi, jules, marie05, nil), les symboles (comme « :- » ou « + ») et les
chaînes de caractères (délimités par « ‘ »).
Les nombres peuvent êtres
des entiers (34, -45, +12) ou des réels (-34.14,0.987, -0.4e+2).
• Les variables qui
débutent par une majuscule (Fils, X, Z67, U_02) ou « _ » (qui correspondent à
une inconnue anonyme ou muette comme _, _X1).
• Les structures (ou terme
composé) permettent de regrouper des éléments reliés logiquement. Elles
débutent par un atome (dénommé foncteur) puis entre parenthèses, les arguments
séparés d’un virgule (« , »). Exemple :
parent(anne,hubert).
roi(louisXV, france, regne(1715, 1774), bourbon).
employe(nom,numero,date(jour,mois,an)).
point(x,y).
segment(point(x1,y1),point(x2,y2)).
triangle(point(x1,y1),point(x2,y2),point(x3,y3)).
Ouvrage (auteur(‘Adam Smith’, annee(1723, 1790)),economie, ouvrage(‘la Richesse
des Nations', 1776),cote(yl8,2345)) .
Le foncteur est le nom de
la relation et l’arité le nombre d’arguments d’une relation.
2. Opérateurs
• Les opérateurs en Prolog
sont des atomes (composés de symbole(s)) qui sont des foncteurs. On les
rencontre essentiellement pour le calcul arithmétique. Prolog permet d’écrire
les opérations de base comme :
+(3,4)
*(3,4)
+(4,(5,5))
Mais la notation préfixée
n’est pas toujours la meilleure présentation et l’on préfère écrire : 3+4, 3 *
4 ou 4 + 5 * 5 ou, pour certains opérateurs, soit en notation préfixée (-3,
√n), soit en notation postfixée (n !).
En tout cas, l’emploi de
parenthèses permet de préciser l’ordre de l’évaluation. Mais, en l’absence de
celles-ci, une expression comme « 4 + 5 * 5 » possède deux évaluations
possibles. Il faut donc préciser :
- la préséance des opérateurs ;
- l’associativité.
La préséance se définit
habituellement par un ordre de priorité (avec 1 la plus forte priorité).
Habituellement, les opérateurs binaires « * » et « / » ont une priorité plus
forte que « + » et « - » (mais attention l’opérateur unitaire « + » et « - »
aura une plus forte priorité que « * » et « / »).
L’associativité permet de
dire si l’expression « 16/4/2 » doit être évaluée comme « (16/4)/2 »
(associatif à gauche) ou comme « 16/(4/2) » (associatif à droite).
En Prolog, les opérateurs
peuvent être définis par le prédicat
op(ClasseDePriorité, Notation, Nom).
Dans laquelle, la notation
est fx (préfixée), xfy (infixée) ou xf (postfixée) pour « f » l’opérateur, « x
», « y » les arguments et « y » pour un argument de même ou de priorité
supérieure (dont ClasseDePriorité plus petit), et « x » pour un argument de
priorité supérieure.
Les opérations
arithmétiques se définissent ainsi :
op(500,yfx,’+’).
op(500,yfx,’-’).
op(400,yfx,’/’).
op(400,yfx,’//’).
op(400,yfx,’*’).
op(400,yfx,’mod’). % nom pour un opérateur
op(200,fy,’-’)
3. L’unification (« = »)
• L’unification n’est pas
un simple « pattern matching » (appariement exact de modèles) qui
retourne vrai si deux termes sont égaux. L’unification rend égaux les deux
termes. Et il faut bien le comprendre pour programmer en Prolog.
Appel
?- X = Y
Dans ce cas, l’interprète
utilise son algorithme d’unification pour rendre égaux X et Y. Trois cas sont
possibles pour une unification réussie, à savoir :
1. X et Y sont des
constantes et elles sont égales ;
2. X ou Y est (sont) une
variable libre ; dans ce cas il y a succès et la variable libre est liée au
terme représenté par la seconde variable (si les deux sont des variables
libres, elles co-référencent alors sur le même objet) ;
3. X et Y sont des termes
composés. Il y a succès si les deux ont le même foncteur, le même nombre
d’arguments et si les arguments (dans l'ordre) s’unifient l’un avec l’autre.
Appel
?- alpha = alpha
?- 23 = 24
?- 23 = alpha
?- alpha = X
?- N = 24
?- N = M
?- N = date(6,juin,1944).
?- lettre(C) = mot(toto)
?- syntagme(D,Nom,Adj) = syntagme(X,Y,Z)
?- auteur(X, action) = auteur(smith, action)
?- auteur(dijsktra, routing) = auteur(X, Y, A)
?- foo(X, X) = foo(a, b)
?- foo(X, a(b,c)) = foo(Z, a(Z,c))
?- a(b,C,d(e,F,g(h,i,J))) = a(B,C,d(E,f,g(H,i,j)))
4. Arithmétique
• L’arithmétique en Prolog
se base sur les opérateurs suivants :
X + Y
X - Y
X * Y
X ^ Y % X a la puissance Y
X / Y
X // Y % division entière
X mod Y % modulo (reste de la division) • Que donne la question suivante :
?- 2 + 3 = 5
Prolog est basé sur une
évaluation paresseuse des expressions arithmétiques. Il faut utiliser
l’opérateur is pour forcer l’évaluation numérique (avec l’absence de toute
inconnue dans la partie droite du is) !
• Voici quelques exemples
X is 22/5
X = 4.4
X is 22//5
X = 4
X is 14 mod 3
X = 2 • Et rien ne vous empêche de chercher tous les nombres …
entier(0).
entier(N) :- entier(N1), N is N1+1.
Pour les comparaisons de nombres
(mais pas de variables ou de termes composés), on peut utiliser les
opérateurs suivants :
X =:= Y % vrai si X et Y représente le même nombre
X =\= Y % vrai si X et Y représente des nombres différents
X < Y % vrai si X est strictement plus petit que Y
X > Y
X =< Y % et non pas <=
X >= Y % et non pas =>
• Avec un petit peu
d’arithmétique, on peut donner les règles (foncteur gcd) pour trouver le plus
grand diviseur commun (soit D) de deux nombres (soit X et Y), on peut utiliser
l’algorithme d’Euclide qui correspond aux règles suivantes : 1. si X = Y, alors
D = X ; 2. si X < Y, alors D est le gcd de X et de la différence Y – X ; 3.
si Y < X, alors utiliser la règle 2 en changeant X et Y. Il s’écrit de la
manière suivante :
gcd(X,X,X).
gcd(X,Y,D) :- X < Y, Y1 is Y-X, gcd(X,Y1,D).
gcd(X,Y,D) :- Y < X, gcd(Y,X,D). Autre exemple, le nombre de Fibonacci :
fibo(1,1).
fibo(2,1).
fibo(N,F) :- N > 2, N1 is N-1, fibo(N1,F1),
N2 is N-2, fibo(N2,F2), F is F1+F2.
5. Listes
La liste (séquence
ordonnée d'éléments de longueur variable) est la principale structure en
Prolog. Les éléments peuvent être des constantes (atomes ou nombres) ou des
termes composés. Pour l’interpréteur Prolog, la liste peut être :
- une liste vide notée []
ou .() ;
- une liste composée d’un
élément de tête et d’un reste comprenant la liste sans le premier élément ;
cette idée se note [T|R].
Les éléments d’une liste
sont séparés (comme les arguments) par une virgule « , ». Exemple de listes :
[a] % ou .(a, []).
[a,b,c] % ou .(a, .(b, .(c, []))).
[la,souris,grise,trottine].
[jean,aime,[les,vins,canadiens]].
[la,souris,grise,trottine].
[jan,fev,mar,avr,mia,juin,juil,aout,sept,oct,nov,dec].
[tennis, ski, musique].
[bach, mozart, beethoven, beatles].
Les deux notations sont
possibles mais évidemment la notation avec des parenthèses droites est plus
simple. Mais les formes suivantes sont aussi équivalentes :
[a,b,c] = [a|[b,c]] = [a,b|[c]] = [a,b,c|[]]
On utilise aussi des
modèles (pattern) pour imposer un certain type de listes, comme, par
exemple :
[X]. % liste composée d’un élément
[X,Y]. % liste composée de deux éléments
[X,Y,Z]. % liste composée de trois éléments
Les unifications suivantes
sont possibles :
[X] = [tintin].
[X] = [123].
[X] = [roi(tintin,Belgique)].
[X] = [[la, souris, grise]].
• Grâce à la notation
[T|R], on peut traiter des listes de longueur variable car on dit simplement
qu’une liste est composée d’un élément en tête (ou d’une tête) et d’un reste
(éventuellement vide) qui est la liste sans son premier élément.
• Les listes suivantes
peuvent s’unifier si :
Prédicats (récursifs) sur les listes
• Il est très utile de
savoir si un élément apparaît (au moins une fois) dans une liste.
Habituellement, ce prédicat se nomme member et correspond aux règles suivantes
:
1. c’est vérifié si la
tête de liste est l’élément recherché ;
2. si l’élément recherché
est quelque part dans le reste de la liste. Il s’écrit de la manière suivante :
member(X,[X|_]). % ou member(X,[Y|_]) :- X=Y.
member(X,[_|R]) :- member(X,R).
Un élément appartient à
une liste si c’est le premier élément (cas simple) ou bien s’il apparaît dans
le reste (soit la liste sans le premier élément, via un appel récursif).
• Le prédicat islist(L)
qui retourne true si L est une liste. C’est vrai si :
1. c’est une liste vide ;
2. le reste est une liste,
quelque soit le premier élément.
islist([]). % une liste vide est une liste
islist([_|R]) :- islist(R). % ou islist([T|R]):-islist(R).
Le comportement attendu,
par exemple avec islist(X) ? Mais on peut s’appuyer simplement sur
l’unification.
• Le prédicat length(L,N)
qui retourne true si L est une liste composée de N éléments. Les règles sont les
suivantes :
1. la liste vide a une
longueur de 0 ;
2. la longueur est 1 de
plus que la liste sans l’élément de tête.
length([],0). % si la liste est vide, c’est 0
length([_|R],N) :- length(R,Nr), N is Nr + 1.
• Le prédicat
append(L1,L2,L) qui retourne true si L est une liste composé des éléments de L1
puis des éléments de L2.
append([],L,L). % si la 2e est vide, c’est simple
append([T|R1],L,[T|R2]) :- append(R1,L,R2).
0 Commentaires