La trace en Prolog
Jean-François Perrot
- L'ordre de trace
- Le littéral-but est considéré comme placé dans une boîte
- Dans le début de trace ci-dessus, on peut lire
- Interaction avec l'utilisateur
- Observation du retour-arrière (Redo)
- Effet du coupe-choix
- Arrêter la trace
Dans tout système de programmation interactif, l'idée de trace
correspond à un mode de fonctionnement de l'exécuteur
qui visualise les différentes phases de l'exécution, en général dans un
perspective de mise au point.
Notamment, dans le cas d'un programme récursif, la trace fait
apparaître l'arbre des appels.
En raison de son schéma d'exécution complexe (en zig-zag),
Prolog a un mode trace particulier,
qui a été formalisé dans le livre classique de Clocksin & Mellish.
[W. F. Clocksin and C. S. Mellish. Programming in Prolog.
Springer-Verlag, New York, Third, Revised and Extended edition, 1987]
Nous prendrons comme exemple le programme arbgen1
, avec les données familiales
initiales.
-
s'applique à la démonstration d'un littéral-but.
Nous choisissons comme exemple le littéral arbgen1(cesar)
, dont la
résolution donne normalement
?- arbgen1(cesar).
cesar
jules
silvia
false.
Lancement de la trace [pour l'arrêter, voir in fine]:
?- trace, arbgen1(cesar).
Call: (8) arbgen1(cesar) ? creep
Call: (9) ecrire(cesar, 0) ? creep
Call: (10) tab(0) ? creep
Exit: (10) tab(0) ? creep
Call: (10) write(cesar) ? creep
cesar
Exit: (10) write(cesar) ? creep
Call: (10) nl ? creep
Exit: (10) nl ? creep
Exit: (9) ecrire(cesar, 0) ?
...........................
Lorsque le calcul se termine normalement (et non par "abort
", commandé par l'option 'a
', voir plus loin sect. 4),
Prolog este en "mode trace", et le manifeste en demandant
[trace] ?-
au lieu de
?-
Pour revenir à la boucle d'interaction ordinaire, dites
[trace] ?- notrace, nodebug.
true.
-
munie de
2 entrées et de 2 sorties (globalement appelées les 4 ports)

[figure extraite des notes de cours de Jacques Tisseau et Fred Mesnard, pl. 52-64.]
Call
désigne le lancement de la
démonstration
Fail
signifie que tout espoir de
démontrer le but doit être abandonné
Exit
annonce une démonstration réussie
(mais il peut s'en trouver d'autres...)
Redo
signale qu'on essaie une nouvelle
fois de démontrer le but, en essayant la clause suivante dans le paquet
de clauses compétent.
Dans le cas d'un prédicat évaluable comme tab
, write
ou nl
, Call
est immédiatement suivi
de Exit
.
Dans celui d'un prédicat défini par un paquet de clauses, chaque
Call
vise une clause du paquet,
et il est suivi d'une séquence de Call
s aux
différents littéraux qui composent le corps de la clause.
Ces appels correspondent à une descente dans l'arbre des appels
récursifs à l'exécuteur, et la profondeur courante est affichée entre parenthèses.
-
que l'appel
initial à
arbgen1(cesar)
s'est produit à la profondeur 8,
qu'il a déclenché un appel en profondeur 9 à ecrire(cesar, 0)
,
- qui lui-même a déclenché un appel en profondeur 10 à
tab(0)
,
lequel a réussi immédiatement,
- puis un appel à
write(cesar)
, encore en
profondeur 10, qui a réussi en laissant cesar
à
l'écran,
- enfin un appel à
nl
, toujours en profondeur 10,
qui a réussi en laissant un saut de ligne à l'écran
- après quoi le
Call: (9) ecrire(cesar, 0)
a
réussi.
Ce que nous avons vu correspond à l'exécution du fragment de code
Prolog :
arbgen1(X) :- ecrire(X, 0), ...
ecrire(X, N) :- tab(N), write(X), nl.
On s'attend donc à ce que la suite commence par Call:
(9) genarb1(cesar, 10)
...
-
Reste à expliquer les indications "
? creep
".
Elles sont produites par l'interaction avec l'utilisateur :
- à chaque étape, l'interprète envoie un point d'interrogation
demandant à l'utilisateur quel est son souhait
- en l'occurrence, l'utilisateur a tapé retour-chariot,
interprété comme "passer à l'étape suivante" - en rampant (angl. to
creep)
- il aurait pu demander autre chose, notamment
- '
a
' (comme abort) qui aurait
mis fin au calcul et ramené à la boucle de lecture de Prolog ;
- '
l
' (comme leap) qui aurait
mis fin au processus de trace et terminé le calcul "normalement" ;
- '
s
' (comme skip) qui aurait
sauté (exécuté sans trace) les appels de profondeur supérieure
et conduit au prochain port sur le même but.
Liste des tracer options dans la doc.
C'est l'option 's
' la plus intéressante : dans
notre exemple, le prédicat ecrire
étant sans mystère est
avantageusement skipped :
?- trace, arbgen1(cesar).
Call: (9) arbgen1(cesar) ? creep
Call: (10) ecrire(cesar, 0) ? skip
cesar
Exit: (10) ecrire(cesar, 0) ? creep
Call: (10) genarb1(cesar, 10) ? creep
...........................
-
Suite de l'exemple :
Call: (10) genarb1(cesar, 10) ? creep
---------|
Call: (11) genp1(cesar, 10) ?
creep ---------| -- première clause de genarb1
Call: (12) pere(_L198, cesar) ? creep ----|
|
Exit: (12) pere(jules, cesar) ? creep ----|
|
Call: (12) ecrire(jules, 10) ? skip
| |
jules
| |
Exit: (12) ecrire(jules, 10) ? creep
| |
Call: (12) sgenarb1(jules, 10) ?
skip | |
Fail: (12) sgenarb1(jules, 10) ?
creep | |
Redo: (12) pere(_L198, cesar) ? creep
----| |
Fail: (12) pere(_L198, cesar) ? creep ----|----| -- un
seul père
Fail: (11) genp1(cesar, 10) ? creep
|
Redo: (10) genarb1(cesar, 10) ? creep
---------|
Call: (11) genm1(cesar, 10) ?
creep ------------- seconde clause de genarb1
Call: (12) mere(_L191, cesar) ? creep ----|
Exit: (12) mere(silvia, cesar) ? creep ----|
Call: (12) ecrire(silvia, 10) ?
skip |
silvia
|
Exit: (12) ecrire(silvia, 10) ? skip
|
Call: (12) sgenarb1(silvia, 10) ? skip
|
Fail: (12) sgenarb1(silvia, 10) ? creep |
Redo: (12) mere(_L191, cesar) ? creep ----|
Fail: (12) mere(_L191, cesar) ? creep ----| -- une
seule mère
Fail: (11) genm1(cesar, 10) ? creep
Fail: (9) arbgen1(cesar) ? creep
false.
-
Même examen sur le programme arbgen2 : les retours-arrière ont disparu.
C'est dû au changement de la définition dans genarb2
, qui est séquentiel,
et aux coupe-choix dans genp2
et dans genm2
.
?- trace, arbgen2(cesar).
Call: (9) arbgen2(cesar) ? creep
Call: (10) ecrire(cesar, 0) ? skip
cesar
Exit: (10) ecrire(cesar, 0) ? creep
Call: (10) genarb2(cesar, 10) ? creep
Call: (11) genp2(cesar, 10) ? creep
Call: (12) pere(_L198, cesar) ? creep
Exit: (12) pere(jules, cesar) ? creep
Call: (12) ecrire(jules, 10) ? skip
jules
Exit: (12) ecrire(jules, 10) ? creep
Call: (12) sgenarb2(jules, 10) ? skip
Pere inconnu
Mere inconnue
Exit: (12) sgenarb2(jules, 10) ? creep
Exit: (11) genp2(cesar, 10) ? creep
Call: (11) genm2(cesar, 10) ? creep -- en séquence
Call: (12) mere(_L198, cesar) ? creep
Exit: (12) mere(silvia, cesar) ? creep
Call: (12) ecrire(silvia, 10) ? skip
silvia
Exit: (12) ecrire(silvia, 10) ? creep
Call: (12) sgenarb2(silvia, 10) ? skip
Pere inconnu
Mere inconnue
Exit: (12) sgenarb2(silvia, 10) ? creep
Exit: (11) genm2(cesar, 10) ? creep
Exit: (10) genarb2(cesar, 10) ? creep
Exit: (9) arbgen2(cesar) ? creep
true.
-
Arrêter la trace
Lorsqu'on a fini d'examiner le comportement d'un prédicat, y compris par l'option abort
,
il suffit que le calcul ait réussi une fois (annonce true .
) pour que la trace reste active,
comme le montre l'invite de la boucle top-level :
[trace] ?-
Pour revenir à une interaction normale, comment s'en débarrasser ?
Il faut demander nodebug
.
% Execution Aborted
[trace] ?- nodebug.
true.
?-
N.B. Si vous demandez seulement notrace
, vous aurez ensuite à demander nodebug
.
[trace] ?- notrace.
true.
[debug] ?- nodebug.
true.
?-