Difficile d'expliquer ce code sans expliquer le fonctionnement de prolog…
Pour simplifier, on va dire que prolog fait du pattern-matching sur les variables (tout ce qui commence par une majuscule ou _
). L'équivalent d'une fonction dans le monde prolog est un prédicat, mais ici l'usage du prédicat npi/3
ressemble fort à celui d'une fonction récursive.
Lorsqu'il y a plusieurs prédicats de même signature (nom + arité), ils sont testés séquentiellement par un mécanisme de backtracking, ce qui permet de poser un prédicat spécial comme condition de sortie.
Et comme il s'agit de prédicats et qu'il n'y a pas de valeur de retour, il est d'usage d'utiliser une variable d'entrée pour stocker le résultat grace au mécanisme d'unification (voir ci-dessous).
Le concept central de prolog est celui d'unification entre deux termes, qui fonctionne dans les deux sens et donne au langage sa forme déclarative.
Par exemple lorsque j'écris [Head | Tail] = Lst
, ça signifie que Lst
est obligatoirement une liste d'au moins un élément en tête qui sera lié à Head
, le reste de la liste étant lié à Tail
.
Si certaines de ces variables sont déjà liées, il faudra que l'égalité soit respectée pour que l'unification réussisse.
Une fois certaines variables unifiées, la liaison de l'une d'elles est répercutée sur les autres. (Par ex. si j'écris [Head | Tail] = Lst, Lst = [1,2,3]
, alors j'aurais Head = 1
et tail = [2,3]
).
Voici mon code commenté, en déroulant les unifications qui étaient implicites sur le premier code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | npi(Symboles, Stack, Result) :-
[Nb | Tail] = Symboles, % unifie Nb au premier élement de Symboles
% et Tail au reste de la liste
number(Nb), !, % si Nb est un nombre, on continue *obligatoirement *
% (sinon, le prochain predicat sera testé)
NStack = [Nb | Stack], % on push Nb au sommet se Stack pour former NStack
npi(Tail, NStack, Result). % appel récursif, Result sera lié par la suite.
npi(Symboles, Stack, Result) :-
[Op | Tail] = Symboles, % unifie Op au premier élement de Symboles
% et Tail au reste de la liste
[V1, V2 | CStack] = Stack, % pop V1 et V2 de Stack, la stack est maintanant CStack
Expr =.. [Op, V1, V2], % univ, pour créer l'expression Expr = Op(V1, V2)
Val is Expr, % Expr est évaluée et le résultat stocké dans Val
Nstack = [Val | CStack], % on push Val sur CStack, la stack devient NStack.
npi(Tail, NStack, Result). % appel récursif, Result sera lié par la suite.
npi(Symboles, Stack, Result):- % (dernier appel)
Symboles = [], % s'il ne reste plus de symboles à lire
[Result] = Stack. % Result est lié au seul élément restant dans Stack
npi(Symboles, Result) :- % prédicat d'arité 2 fourni à l'utilisateur
npi(Symboles, [], Result).
|
Au final, à l'usage on passe simplement la liste de symboles à évaluer et une variable libre qui va servir à récupérer le résultat.