Recursie 1

Creative Commons License
This work is licensed under a
Creative Commons Attribution-NonCommercial-ShareAlike 4.0
International License
.

Editor:

John Val

Bron: Learn Prolog Now!

recursieve definities vragen

Wat leer je in dit hoofdstuk:

Recursie

In de cursussen Javascript en PhP hebben we het concept herhaling ( = iteratie ) behandeld. In één functie wordt een zelfde code meerdere malen herhaald :

<script> // Voorbeeld javascript iteratie: het optellen van 1 t/m 100: function som(begin,eind) { var antwoord=0; for(var i=begin; i <= eind; i++ ) { antwoord+=i; } return antwoord; } alert(som(1,100)); </script>
In Prolog is deze vorm van herhaling niet mogelijk. Vaak is het ook in Prolog nodig om een stuk code meerdere malen achter elkaar uit te voeren. Dit is te realiseren door gebruik te maken van recursie. Bij recursie wordt er in een functie slechts één berekening gemaakt waarbij één element van de berekening het antwoord is van de aanroep van de zelfde functie. Niet alleen Prolog biedt deze mogelijkheid, in bijna alle andere talen kan dit ook. Het voorbeeld hierboven in Javascript omgezet naar recursie is: :
<script> // Voorbeeld javascript recursie: het optellen van 1 t/m 100: function somR(n,eind) { if(n >= eind) return eind; else return n + somR(n+1,eind); } alert(somR(1,100)); </script>
Je ziet dat de functie somR niet direct de herhaling doet, maar dat het herhalingsproces tot stand komt door het aanroepen van dezelfde functie binnen de functie, ofwel recursie. Na dit uitstapje naar Javascript gaan we recursie implementeren in Prolog. Maakt het Javascript voorbeeld voor jou recursie nog niet helemaal duidelijk? Hopelijk ben je na de rest van dit hoofdstuk helemaal op de hoogte. Misschien herken je het begrip recursieve formule ook uit rijen en reeksen in de wiskunde: een volgende term in de rij wordt berekend met het huidige element in de rij.
Voorbeeld wiskunde: Voor de rij u : 2,4,6,8 , ... geldt: recursieve definitie van de rij: u(n+1) = u(n) + 2; u(1) = 2 ; n = 1,2,3, ... directe formule; u(n)= 2 n; n = 1,2,3, ...

recursieve definities

Terug bij Prolog. Recursie kan alleen plaatsvinden in clausules (rules, clausules). Predicaten kunnen recursief worden gedefinieerd. Kort door de bocht, een predicaat is recursief gedefinieerd als één of meerdere clausules naar zichzelf refereren. In de vragen bij les 2: elementen heb je recursie al een keer gezien. We bekijken nog een paar voorbeelden waarin we uitleggen hoe een en ander werkt.

Voorbeeld 1: Eten

Beschouw de volgende kennisbank:

is_aan_het_verteren(X,Y) :- net_gegeten(X,Y). is_aan_het_verteren(X,Y) :- net_gegeten(X,Z), is_aan_het_verteren(Z,Y). net_gegeten(mug,bloed(john)). net_gegeten(kikker,mug). net_gegeten(ooievaar,kikker).
Op het eerste gezicht lijkt dit niet erg nieuw: het is gewoon een kennisbank met drie feiten en twee regels. Maar de definitie van de tweede clausule van het predicaat is_aan_het_verteren/2 is recursief. Je ziet namelijk dat in de body van is_aan_het_verteren/2, is_aan_het_verteren/2 weer aanwezig is. Dit zou in principe tot een oneindige serie van aanroepen van is_aan_het_verteren/2 kunnen leiden (vergelijk de wiskundige rij hierboven). Echter in de informatica moeten de meeste recursieve processen eindig of te beëindigen zijn. Er moet dus altijd een ontsnappingsmogelijkheid aanwezig zijn voor een goed werkend programma. In ons voorbeeld is dat de eerste clausule van het predicaat is_aan_het_verteren/2 (Bepalend is dat in de eerste clausule is_aan_het_verteren/2 niet in de body aanwezig is ). We bekijken nu de declaratieve en procedurele betekenis van deze definitie.

Het woord declaratief wordt gebruikt om te praten over de logische betekenis van Prolog kennisbanken. De declaratieve betekenis van een Prolog kennisbank is eenvoudig wat het zegt of wat het betekent, als we het lezen als een verzameling van logische beweringen. De declaratieve betekenis van de recursieve definitie is recht toe recht aan. De eerste clausule zegt (de niet recursieve die we ook wel de basis clausule noemen): als X net Y heeft gegeten, dan is X nu Y aan het verteren. Dit is natuurlijk een zinvolle definitie.

De tweede clausule ( de recursieve ) zegt: Als X net Z heeft gegeten en Z is Y aan het verteren dan is X ook Y aan het verteren. Ook weer een zinvolle definitie.

We weten nu wat het hele predicaat is_aan_het_verteren/2 ons verteld, maar wat gebeurt er als we een vraag stellen met deze definitie, ofwel hoe wordt deze vraag door Prolog beantwoord, een procedurele bewerking. In de Prolog terminologie is dit: wat is de procedurele betekenis?.

Ook de procedurele betekenis is recht toe recht aan. De definitie van de basis clausule heeft precies de zelfde vorm als die van clausules die we eerder hebben gezien. Als we vragen of X Y aan het verteren is, gebruikt Prolog deze regel om de vraag om te zetten naar de vraag: heeft X net Y opgegeten?

Recursie in beeld

Als X niet Y heeft gegeten dan gaat Prolog naar de tweede clausule en stelt dan de vraag of X net Z heeft opgegeten en als Z Y aan het verteren is dan verteert X ook Y. De tweede clausule wordt dus met twee subvragen beantwoord. Hopelijk eindigt het proces van vragen stellen in het stellen van vragen waarvan de antwoorden direct opgezocht kunnen worden in de feiten van de kennisbank. De figuur hiernaast geeft een grafisch beeld van de situatie.

De vraag of Z Y aan het eten is, wordt dan ook eerst weer door de eerste clausule beantwoord, ofwel heeft Z Y net opgegeten? Ook nu kan het weer zo zijn dat Z een ander beest heeft gegeten dat Y al aan het verteren is, enzovoorts.

We gaan eens kijken hoe dit voorbeeld werkt. Kopieer de bovenstaande kennisbank naar SWISH en stel eens de volgende vraag.

?- is_aan_het_verteren(ooievaar,mug).
Het antwoord hierop is true. Prolog heeft deze conclusie als volgt getrokken. Eerst wordt de eerste (de basis) clausule aangeroepen, simpel omdat dit de eerste clausule is die aanwezig is. Prolog zet dit om naar de vraag
?- net_gegeten(ooievaar,mug).
Het antwoord op deze vraag is false, want dit feit is niet in de kennisbank aanwezig. Backtracking zorgt er dan voor dat Prolog de tweede clausule van is_aan_het_verteren/2 gaat beantwoorden. Deze wordt omgezet naar de samengestelde vraag
?- net_gegeten(ooievaar,Z), is_aan_het_verteren(Z,mug).
Prolog moet nu in kennisbank op zoek naar een Z die zowel de vragen
?- net_gegeten(ooievaar,Z).
en
?- is_aan_het_verteren(Z,mug).
positief beantwoord. En gelukkig als Z gelijk wordt gemaakt aan kikker dan is het duidelijk dat
net_gegeten(ooievaar,kikker)
waar is en kan de vraag
?- is_aan_het_verteren(kikker,mug).
worden omgezet in de vraag
?- net_gegeten(kikker,mug).
die ook weer feitelijk waar is.

Dit was ons eerste voorbeeld van een predicaat dat recursief is gedefinieerd. Voor we nog meer over recursieve clausules leren maken we nu eerst een praktische opmerking. Hopelijk is het uit het bovenstaande duidelijk dat als je een recursief predicaat schrijft, je altijd minstens twee clausules nodig hebt. Één basis clausule waarmee je de recursie kan stoppen en één die de recursie bevat. Als je alleen de recursieve clausule maakt dan kan Prolog eindeloos doorgaan met aanroepen van die recursie. Een voorbeeld van zo'n een slechte kennisbank is:

p :- p.
Dat is alles. Niets meer. Het is mooi door de eenvoudigheid ervan. Vanuit een declaratief standpunt niet eens zo onzinnig omdat het zegt: "Als eigenschap p waar is dan volgt daar uit dat eigenschap p waar is". Niets tegen in te brengen.

Procedureel gezien is dit een beestachtig gevaarlijke clausule. Dit is namelijk de uiterste van de gevaarlijke recursieve clausules: precies het zelfde ding aan beide zijden van de nek en geen basis clausule om te ontsnappen. Bedenk maar eens wat er gebeurd als we de volgende vraag stellen

?- p.
Prolog vraagt aan zichzelf: "Hoe bewijs ik p" en trekt de conclusie: "Hé daar heb ik een clausule voor! Om te bewijzen dat p waar is met ik alleen maar te bewijzen dat p waar is." Dus moet het weer aan zichzelf vragen: "Hoe bewijs ik p" en trekt de weer de conclusie: "Hé daar heb ik een clausule voor! Om te bewijzen dat p waar is met ik alleen maar te bewijzen dat p waar is." Dus moet het weer aan zichzelf vragen: "Hoe bewijs ik p" en trekt de weer de conclusie: "Hé daar heb ik een clausule voor! Om te bewijzen dat p waar is met ik alleen maar te bewijzen dat p waar is." En zo maar door.

Als je deze vraag stelt in Swish krijg je geen antwoord. Uiteindelijk zal je de vraag moeten afbreken.

Voorbeeld 2: Afstammeling

Nu we iets weten van recursie in Prolog wordt het tijd om ons af te vragen waarom het zo belangrijk is. Deze vraag kan op verschillende niveaus in Prolog programmeren worden beantwoord, maar hier houden we het redelijk praktisch. Hier beantwoorden we de vraag: Als we iets zinnigs in Prolog willen programmeren is recursie dan werkelijk zo belangrijk? En zo ja waarom?

Prolog code kennisbank geboorten
% kennisbank geboorten
	
persoon(geert,man).
persoon(bert,man).
persoon(madelief,vrouw).
persoon(louise,vrouw).
persoon(patrick,man).
persoon(karin,vrouw).
persoon(sylvia,vrouw).
persoon(karel,man).
persoon(harry,man).
persoon(johan,man).
persoon(truus,vrouw).
persoon(henk,man).
persoon(carola,vrouw).

kindVan(geert,harry).
kindVan(geert,louise).
kindVan(bert,patrick).
kindVan(bert,karin).
kindVan(madelief,patrick).
kindVan(madelief,karin).
kindVan(karin,karel).
kindVan(karin,sylvia).
kindVan(sylvia,truus).
kindVan(sylvia,johan).
kindVan(harry,johan).
kindVan(harry,truus).
kindVan(johan,henk).
kindVan(henk,carola).

	

Zoals altijd verduidelijken we de vraag en het antwoord met een voorbeeld. We bekijken weer de kennisbank van geboorten uit les 1 en gaan dan uitleg geven over de vragen uit les 2. We pakken een paar feiten uit de kennisbank:

kindVan(geert,harry). kindVan(harry,johan).
Deze feiten zeggen dat Geert een kind van Harry is en dat Harry een kind van Johan is. Als nu een afstammelingsrelatie in onze kennisbank willen aanbrengen dan is Geert een afstammeling van zowel Harry (= vader) als Johan (= opa). Met deze twee feiten kunnen we op de volgende manier, nog zonder recursie, clausules opstellen die deze afstammelingsrelatie weergeven (probeer uit in Swish):
is_afstammeling_van(X,Y):- kindVan(X,Y). is_afstammeling_van(X,Y):- kindVan(X,Z), kindVan(Z,Y).
Voor relaties t/m grootouder (oma/opa) gaat dit goed, (Stel maar eens de vraag ?- is_afstammeling_van(geert,johan).) maar als we nog verder terug gaan in de tijd (overgrootouder, betovergrootouder, ...)
kindVan(geert,harry). kindVan(harry,johan). kindVan(johan,henk). kindVan(henk,carola).
dan gaat onze eerste afstammelingsrelatie mis. Geert is duidelijk een afstammeling van Carola, maar de vraag ?- is_afstammeling_van(geert,carola). wordt negatief beantwoord. Nu kunnen we natuurlijk meerdere versies zoals clausule 2 maken
is_afstammeling_van(X,Y):- kindVan(X,Y). is_afstammeling_van(X,Y):- kindVan(X,Z), kindVan(Z,Y). is_afstammeling_van(X,Y):- kindVan(X,Z), kindVan(Z,A), kindVan(A,Y). is_afstammeling_van(X,Y):- kindVan(X,Z), kindVan(Z,A), kindVan(A,B), kindVan(B,Y).
De evolutie van de mens
om Geert een afstammeling van Carola te laten zijn, maar waar en waarom stop je? Behalve dat je niet weet hoe diep je in een stamboom terug zal moeten is het geen mooie en efficiënte oplossing. We kunnen de diepgang in de stamboom rustig naar Adam en Eva terugvoeren (of nog verder gaan als evolutie jouw ding is) met slechts twee clausules, maar dan kunnen we niet zonder recursie.
is_afstammeling_van(X,Y):- kindVan(X,Y). is_afstammeling_van(X,Y):- kindVan(X,Z), is_afstammeling_van(Z,Y).
Elegant hè! De declaratieve betekenis van de basis clausule is: Als X een kind van Y is dan is X een afstammeling van Y. De recursieve clausule zegt: Als X een kind van Z is en Z is een afstammeling van Y dan is X een afstammeling van Y. Een waarheid als een koe!

We bekijken weer hoe Prolog de vraag ?- is_afstammeling_van(geert,carola). beantwoordt. Het onderstaande diagram geeft de boom weergave van de procedure van de zoektocht die we hier stap voor stap toelichten. Eerst kijkt Prolog of de basis clausule (de eerste in de lijst) geldt en vertaalt de vraag

?- is_afstammeling_van(geert,carola).
naar de vraag
?- kindVan(geert,carola).
Omdat kindVan(geert,carola) geen deel uitmaakt van de feiten in de kennisbank loopt dit pad dood. Prolog gaat via backtracking naar de tweede (recursieve) clausule en vertaalt de vraag
?- is_afstammeling_van(geert,carola).
nu naar de samengestelde vraag
?- kindVan(geert,_633),is_afstammeling_van(_633,carola).
Het eerste feit dat Prolog tegenkomt waarin Geert een kind is van iemand is het feit
kindVan(geert,harry).
Dus de variabele _633 wordt gelijkgesteld aan harry waardoor het eerste deel van de samengestelde vraag waar is en Prolof het tweede deel van de samengestelde vraag
?- is_afstammeling_van(harry,carola).
moet beantwoorden. Eerst wordt nu weer de basis clausule gekozen om de vraag te beantwoorden en deze wordt omgezet naar de vraag
?- kindVan(harry,carola).
Omdat kindVan(harry,carola) geen deel uitmaakt van de feiten in de kennisbank loopt dit pad dood. Prolog gaat via backtracking weer naar de tweede (recursieve) clausule en vertaalt de vraag
?- is_afstammeling_van(harry,carola).
nu naar de samengestelde vraag
?- kindVan(harry,_733),is_afstammeling_van(_733,carola).
Het eerste feit dat Prolog tegenkomt waarin Harry een kind is van iemand is het feit
kindVan(harry,johan).
Dus de variabele _733 wordt gelijkgesteld aan johan waarna de vraag
?- is_afstammeling_van(johan,carola).
moet worden beantwoord. Weer wordt eerst de basis clausule gekozen om de vraag te beantwoorden en deze wordt omgezet naar de vraag
?- kindVan(johan,carola).
Omdat kindVan(johan,carola) weer geen deel uitmaakt van de feiten in de kennisbank loopt dit pad ook weer dood. Prolog gaat via backtracking weer naar de tweede (recursieve) clausule en vertaalt de vraag
?- is_afstammeling_van(johan,carola).
nu naar de samengestelde vraag
?- kindVan(johan,_833),is_afstammeling_van(_833,carola).
Het eerste feit dat Prolog tegenkomt waarin Johan een kind is van iemand is het feit
kindVan(johan,henk).
Dus de variabele _833 wordt gelijkgesteld aan henk waarna de vraag
?- is_afstammeling_van(henk,carola).
moet worden beantwoord. Dus weer naar de basis clausule met de vraag
kindVan(henk,carola).
. Nu is er wel het feit kindVan(henk,carola) dus kan Prolog de vraag ?- is_afstammeling_van(geert,carola). dus positief beantwoorden.

In les 4 heb je deze boom structuur van de zoektocht (tree search) ook al gezien. Zorg ervoor dat je dit concept in het beantwoorden van vragen echt begrijpt.

Boomstructuur zoektocht

Dit voorbeeld zou je nu duidelijk moeten hebben gemaakt dat het niet uitmaakt hoeveel opeenvolgende generaties er in de kennisbank aanwezig zijn. Alleen bij een "missing link" gaat het natuurlijk mis. De recursieve formule is zo algemeen en zo compact dat het duidelijk is dat het moet werken.

Recursieve clausules zijn zeer belangrijk. Ze staan je toe om heel veel informatie in een compacte en natuurlijke manier te definiëren. Als je Prolog programmeert zul je meestal bezig zijn met het schrijven van recursieve formules.

Voorbeeld 3: Opvolger

In les 3 hebben we laten zien dat Prolog programmeren is gebaseerd op unificatie en de bewijsvoering wordt vastgelegd door de structuur van het aanbieden van de clausules. Nu we recursie kennen kunnen we interessantere voorbeelden van ditsoort structuren geven.

Tegenwoordig schrijven mensen gehele getallen in een tientallig stelsel (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, enz.) maar er zijn, zoals je waarschijnlijk weet, vele andere notaties. Bijvoorbeeld computers, omdat ze zijn opgebouwd op uit/aan (0/1) elektronische schakelaars, gebruiken een binaire (tweetallig) getalsysteem (bovenstaande rij wordt dan 0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010, 1011, 1100, enz). Andere culturen gebruiken nog weer andere getal systemen. De oude Babyloniers gebruikten een 60-tallig stelsel. De Romeinen maakten er eigenlijk een beetje een rommeltje van, voor 5 (V), 10 (X), 50 (L),100 (C), 500 (D) en 1000 (M) zijn er verschillende symbolen en de volgorde is ook niet recht toe recht aan zo is 999 gelijk aan CMXCIX (terugwerkend) en 888 DCCCLXXXVIII (altijd vooruit). Niet echt handig die Romeinen. Verzin maar eens een systematische manier voor staartdelen in de Romeinse getal notatie. Je zult al snel merken dat dit heel lastig is. Waarschijnlijk hadden de Romeinen daar specialisten voor.


Gebaseerd op dit liedje van Arteha Franklin

Hier presenteren we nog een manier om getallen te schrijven die soms wordt gebruikt in de wiskundige logica en ook in de getaltheorie. (Ook het wiskundige voorbeeld boven in dit hoofdstuk zouden we hier kunnen gebruiken) De manier gebruikt slechts vier symbolen: 0, opvolger, en de linker en rechter haakjes. De getallen wordt dan gedefinieerd met behulp van inductie:
0 is een getal Als X een getal is, dan is opvolger(X) dat ook.
. Opvolger(X) is dan het getal dat je krijgt door één bij X op te tellen. Deze definitie zegt dus dat 0 een getal is en dat alle andere getallen kunnen worden gemaakt door opvolger er voor te zetten. ( In de getaltheorie definieer dus op deze manier alle natuurlijke getallen en is oneindigheid ook bewijsbaar).

Hé denk je nu, deze definitie kunnen julle vast omzetten in een Prolog programma met behulp van recursie. De volgende kennisbank is het resultaat
getal(0). getal(opvolger(X)) :- getal(X).
. Stellen we een vraag als
getal(opvolger(opvolger(opvolger(0)))).
dan krijgen we het antwoord true.

We kunnen ook interessantere vragen stellen. Wat gebeurd er bij de vraag :
getal(X).
We vragen hier eigenlijk "Hé geef eens een paar getallen". De eerste 8 regels van het resultaat zijn dan
X = 0 X = opvolger(0) X = opvolger(opvolger(0)) X = opvolger(opvolger(opvolger(0))) X = opvolger(opvolger(opvolger(opvolger(0)))) X = opvolger(opvolger(opvolger(opvolger(opvolger(0))))) X = opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(0)))))) X = opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(0))))))) X = opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(opvolger(0))))))))
Prolog is inderdaad aan het tellen, maar belangrijker is hoe doet Prolog dit. Simpel gezegd, Prolog is aan het backtracken door de recursieve clausule en maakt getallen door gebruik te maken van unificatie. Dit is een leerzaam voorbeeld en het is belangrijk dat je het begrijpt. Gebruik je het swi-prolog programma dan kun je met het trace commando van Prolog reconstrueren. Hier onder zie je weer een boomstructuur van de zoektocht. Als je de trace (klik op dit pad) vergelijkt met de figuur dan zie je dat de "Redo" commando's verder gaan bij het laatste keuzepunt, de splitsing van twee takken, direct voorafgaand aan laatst gegeven antwoord in de boom. Ofwel nog een keer backtracking in beeld.
Recursie in beeld

Bouwen en gelijkstellen. Recursie, unificatie en de zoektocht naar bewijs. Dit zijn de basisideeën die deel uit maken van de kern van Prolog programmeren. Telkens wanneer we recursief gestructureerde objecten moeten maken of analyseren (zoals bijvoorbeeld de gehele getallen) vormt het samenspel van deze ideeën ervoor dat Prolog een krachtig instrument is om deze taken te verrichten. In het hoofdstuk waar de lijsten ( Lists, rijen ), nog een erg belangrijke component van Prolog, worden geïntroduceerd en in de hoofdstukken over taalverwerking zal deze mix weer de belangrijkste taak krijgen. Vele applicaties (waarvan computationele linguïstiek het belangrijkste voorbeeld is) maken vooral gebruik van recursief gestructureerde objecten ( b.v. bomen en lijsten).

Voorbeeld 4: Optellen

Als laatste voorbeeld voegen we enkele rekenkundige bewerkingen toe aan het vorige voorbeeld. Laten we eens proberen om optellen te definiëren. We willen dan het predicaat telop/3 aan de kennisbank toevoegen. De eerste twee argumenten van dit predicaat worden de twee op te tellen getallen en het derde argument gaat uiteindelijk het antwoord bevatten. We willen dus een vragen kunnen stellen als

?- telop( opvolger(opvolger(0)), opvolger(0), opvolger(opvolger(opvolger(0)))). True ?- telop( opvolger(opvolger(0)), opvolger(opvolger(0)), opvolger(opvolger(opvolger(0)))). False ?- telop( opvolger(opvolger(0)), Y, opvolger(opvolger(opvolger(0)))). Y = opvolger(0)
Er zijn twee belangrijke eigenschappen van optellen die we hierin moeten meenemen om de basis clausules op te stellen.
  1. Als we 0 + 0 willen berekenen (de eerste twee argumenten zijn 0) dan moet het antwoord ook 0 worden.
    ?- telop(0,0,Y). Y = 0
  2. Als we 0 bij een ander getal willen optellen ( één van de eerste twee argumenten is 0) dan moet het antwoord het niet 0 argument zijn.
    ?- telop(0,opvolger(opvolger(0)),Y). Y = opvolger(opvolger(0)) ?- telop(opvolger(opvolger(0)),0,Y). Y = opvolger(opvolger(0))
Stel nu dat we twee getallen X en Y willen optellen ( b.v. X = opvolger(opvolger(opvolger(0))) en Y = opvolger(opvolger(0))) en X en Y zijn beide niet 0. Als we X1 definiëren als het getal dat één opvolger functor minder heeft dan X ( X1 = opvolger(opvolger(0)) in ons voorbeeld ) en als we het resultaat van de optelling van X1 en Y weten ( noem die Z ), dan is het makkelijk om de uitkomst van de optelling van X en Y recursief te definiëren: we hoeven slechts één opvolger functor toe te voegen aan Z. De hele definitie van het optel predicaat wordt dan
telop(0,Y,Y). telop(opvolger(X),Y,opvolger(Z)) :- telop( X, Y, Z).
Wat gebeurt er nu als we onze getal kennisbank uitbreiden met deze definitie en de volgende vraag stellen?
?- telop( opvolger(opvolger(opvolger(0))), opvolger(opvolger(0)), R).
We gaan stap voor stap door de procedurele betekenis van deze vraag heen. De trace van de zoektocht naar R is en de boomstructuur R is weergegeven in de figuur hieronder. Onder deze figuur lichten we de stappen toe.
Recursie telop in beeld
[trace] ?- telop( opvolger(opvolger(opvolger(0))), opvolger(opvolger(0)), R). Call: (8) telop( opvolger(opvolger(opvolger(0))), opvolger(opvolger(0)), _1002) ? creep Call: (9) telop( opvolger(opvolger(0)), opvolger(opvolger(0)), _1240) ? creep Call: (10) telop(opvolger(0), opvolger(opvolger(0)), _1244) ? creep Call: (11) telop(0, opvolger(opvolger(0)), _1248) ? creep Exit: (11) telop(0, opvolger(opvolger(0)), opvolger(opvolger(0))) ? creep Exit: (10) telop(opvolger(0), opvolger(opvolger(0)), opvolger(opvolger(opvolger(0)))) ? creep Exit: (9) telop( opvolger(opvolger(0)), opvolger(opvolger(0)), opvolger(opvolger(opvolger(opvolger(0))))) ? creep Exit: (8) telop( opvolger(opvolger(opvolger(0))), opvolger(opvolger(0)), opvolger(opvolger(opvolger(opvolger(opvolger(0)))))) ? creep R = opvolger(opvolger(opvolger(opvolger(opvolger(0))))).
Het eerste argument in de vraag is niet 0. Dus de recursieve clausule wordt genomen en de variabele R wordt gelijkgesteld aan de tijdelijke variabele _1002 ( trace: Call: (8)) die op dit moment nog geen waarde heeft (= onbepaald ), dit zorgt er ook voor dat R op dit moment nog onbepaald is. De volgende stap is dat we de head van de vraag in Call:(8) gaan omzetten naar de bewering uit de body van de recursieve formule ( trace: Call: (9)). Er wordt dan een functor opvolger van het eerste argument weggehaald, het tweede argument blijft hetzelfde. Het derde argument wordt weer een tijdelijke onbepaalde variabele _1240 waarvoor geldt _1002 = opvolger(_1240).

De calls 9, 10 en 11 worden op vergelijkbare manier opgebouwd, echter de vraag in Call 11, kan worden beantwoord met de basis clausule. De variabele _1248 wordt op dat moment gelijkgesteld (geünificeerd) met Y ofwel

_1248 = opvolger(opvolger(0))
en dus zijn nu ook _1244, _1240, _1002 en R bepaald":
_1244 = opvolger(_1248) = opvolger(opvolger(opvolger(0))) _1240 = opvolger(_1244) = opvolger(opvolger(opvolger(opvolger(0)))) _1002 = opvolger(_1240) = opvolger(opvolger(opvolger(opvolger(opvolger(0))))) R = _1002 = opvolger(opvolger(opvolger(opvolger(opvolger(0)))))
In de boomstructuur zie je de stap voor stap toewijzing van de variabelen naast de verbindingslijnen.

Je ziet dat ook de optelling weer op een mooie compacte en recursieve manier kan worden gedefinieerd.