Page d'accueil encyclopedie-enligne.com en page d'accueil
Liste Articles: [0-A] [A-C] [C-F] [F-J] [J-M] [M-P] [P-S] [S-Z] | Liste Catégories | Une page au hasard | Pages liées

Récursion terminale


image:Langage_progr.png
Cet article fait partie de
la série Langages de programmation
Langages orientés objet
Ada 95 - C++ - C#
Common lisp object system
Delphi - Eiffel - Java - Nice
Langages impératifs
APL - ASP
Assembleur
BASIC - C - Pascal
Perl - PHP - Python
Langages fonctionnels
ML/OCaml - Lisp/Common Lisp
Forth - Logo - Scheme
Langages déclaratifs
Clips - Prolog
Voir aussi
Conception - Codage
Tests - Optimisations


Sommaire

Idée

La récursion terminale est une méthode pour transformer, dans un programme, une récursion en itération : elle s'applique lorsque l'appel récursif dans une fonction est la dernière opération exécutée de cette fonction (ou encore : l'appel n'a pas d'enveloppe).

La récursion terminale est utilisée principalement dans les langages de programmation fonctionnels pour exprimer un processus itératif dans une forme fonctionnelle récursive. Les langages de programmation fonctionnels peuvent généralement détecter la récursion terminale et optimiser l'exécution de façon à itérer, en économisant ainsi l'espace de la pile d'appel, comme décrit ci-dessous.

Exemple

Prenons ce programme Scheme comme exemple :

 (define (factorielle n)
 (define (iterer n acc)
 (if (<= n 1)
 acc
 (iterer (- n 1) (* acc n))))
 (iterer n 1))

On observe que la fonction 'iterer' s'appelle elle-même à la fin de sa définition. Cela permet à l'interpréteur ou au compilateur de réorganiser l'exécution, qui sans cela ressemblerait à ceci :

 call factorielle (3)
 call iterer (3 1)
 call iterer (2 3)
 call iterer (1 6)
 call iterer (0 6)
 retourne 6
 retourne 6
 retourne 6
 retourne 6
 retourne 6

après réorganisation :

 call factorielle (3)
 remplace les arguments avec (3 1), jump into « iterer »
 remplace les arguments avec (2 3), re-iterer
 remplace les arguments avec (1 6), re-iterer
 remplace les arguments avec (0 6), re-iterer
 retourne 6

(l'espace consommé est proportionnel à l'indentation)

Avantages

Cette réorganisation économise de l'espace car aucun état, sauf l'adresse de la fonction appelante, n'a besoin d'être sauvé sur la pile ou sur le tas. Cela signifie également que le programmeur n'a pas à craindre l'épuisement de l'espace de pile ou du tas pour des récursions très profondes.

Certains programmeurs utilisant des langages fonctionnels réécrivent du code récursif enveloppé de façon à tirer parti de cette caractéristique. Cela requiert souvent l'utilisation d'un accumulateur (acc dans l'implémentation ci-dessus de factorielle), comme argument de la fonction en récursion terminale. Dans certains cas (comme pour le filtrage de listes) et dans certains langages, la récursion terminale nécessite de réécrire une fonction dans un style non-fonctionnel : c'est-à-dire une fonction qui altère le contenu de variables passées en argument par référence.

Certains compilateurs utilisent une technique de réécriture de programme en « Continuation Passing Style » qui automatise la transformation d'une récursion enveloppée en récursion terminale. Associée à l'élimination d'appel terminal, cette technique permet d'optimiser le code produit pour des langages fonctionnels.

Pour finir, voici une version enveloppée de factorielle :

 (define (factorielle n)
 (cond ((= n 0) 1)
 (else (* n (factorielle (- n 1))))))

Le résultat de l'appel récursif (factorielle (- n 1)) est utilisé par la fonction *, qui constitue ici l'enveloppe. À cause de cela, le résultat de (factorielle (- n 1)) doit être empilé pour être utilisé par *. On ne peut économiser d'espace de pile.

Origines

Voir le papier de Guy Steele : ftp://publications.ai.mit.edu/ai-publications/0-499/AIM-443.ps

(librement traduit d'après la version anglophone)



This site support the Wikimedia Foundation. This Article originally from Wikipedia. All text is available under the terms of the GNU Free Documentation License Page HistoryOriginal ArticleWikipedia