Note que toi tu as besoin d'un autre "concept": la récursion terminale
Je n'ai pas besoin de ce "concept" pour me convaincre que ces fonctions font ce que je veux. Par ailleurs le fait qu'un appel est terminal ou non peut être vérifié par un critère syntaxique simple. Dans tous les cas son usage ne rend certainement pas le code "moins lisible".
Ta focalisation sur la récursion terminale est à mon avis signe du fait que tu te compliques la vie inutilement. Si tu réfléchis à ce que fais un programme fonctionnel en ayant en tête la façon dont c'est compilé vers du langage machine, l'explication de ce qu'est la récursion terminale est un peu technique et cela paraît être un cas particulier spécifique. Par contre si tu raisonnes simplement en terme de "évaluer c'est réduire le programme étapes par étapes vers le résultat", les bonnes propriétés de la récursion terminale viennent tout naturellement (au lieu de raisonner sur la taille de la pile, on raisonne sur la taille du programme réduit), et on n'a pas besoin d'une explication spécifique pour cela. C'est en fait une façon tout à fait valide et simple de penser aux programmes, indépendamment des aspects bas-niveau de la compilation.
Pas de souci pour fibonacci sauf que tu as du reconstruire la fonction exactement comme en code impératif.
Bien sûr, c'est normal, il n'y a rien de magique à écrire du code fonctionnel. Je n'ai jamais dit, moi, qu'une version serait "beaucoup plus lisible" qu'une autre. Mais c'est vrai que les miennes sont beaucoup plus courtes (grâce au choix opportuniste d'un langage à la syntaxe dépouillée), et aussi plus simples grâce à l'abandon du concept de variable modifiable; je n'ai pas besoin de différencier le calcul de la valeur et son retour.
Sinon pour l'évaluation paresseuse, j'ai lu quelques fois des remarques d'utilisateurs qui trouvaient que ça donnait des performances difficiles a maîtriser..
Je suis d'accord et je ne suis pas pour utiliser la paresse par défaut à outrance. J'ai écrit mes exemples en Haskell parce que la légèreté syntaxique permet d'illustrer mon point très efficacement, mais dans la vraie vie j'utilise plutôt OCaml, qui est un langage strict par défaut.
Par ailleurs il y a des interactions intéressantes entre la paresse et la récursivité : en Haskell on ne recherche pas la récursivité terminale, et elle est même néfaste aux performances en général. Je te laisse creuser le sujet si ça t'intéresse, mais dans tous les cas les fonctions écrites ci-dessus sont strictes de toute façon, donc elles ne seront pas évaluées de façon paresseuse (le compilateur s'en rend bien compte).
Pour le fun, une version de fibonacci qui a la bonne complexité et utilise à fond l'évaluation paresseuse (c'est fun et ça fait un bon exercice de gymnastique intellectuelle, mais je ne recommande pas d'écrire comme ça):
fib n = fibs !! n
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
(zipWith (+) envoie la liste des sommes terme-à-terme de deux listes de même taille, et tail renvoie la liste privée de son premier élément. (!!) est l'accès au n-ième élément d'une liste).
[^] # Re: Hum ...
Posté par gasche . En réponse au journal Votre langage idéal ?. Évalué à 3.
Je n'ai pas besoin de ce "concept" pour me convaincre que ces fonctions font ce que je veux. Par ailleurs le fait qu'un appel est terminal ou non peut être vérifié par un critère syntaxique simple. Dans tous les cas son usage ne rend certainement pas le code "moins lisible".
Ta focalisation sur la récursion terminale est à mon avis signe du fait que tu te compliques la vie inutilement. Si tu réfléchis à ce que fais un programme fonctionnel en ayant en tête la façon dont c'est compilé vers du langage machine, l'explication de ce qu'est la récursion terminale est un peu technique et cela paraît être un cas particulier spécifique. Par contre si tu raisonnes simplement en terme de "évaluer c'est réduire le programme étapes par étapes vers le résultat", les bonnes propriétés de la récursion terminale viennent tout naturellement (au lieu de raisonner sur la taille de la pile, on raisonne sur la taille du programme réduit), et on n'a pas besoin d'une explication spécifique pour cela. C'est en fait une façon tout à fait valide et simple de penser aux programmes, indépendamment des aspects bas-niveau de la compilation.
Bien sûr, c'est normal, il n'y a rien de magique à écrire du code fonctionnel. Je n'ai jamais dit, moi, qu'une version serait "beaucoup plus lisible" qu'une autre. Mais c'est vrai que les miennes sont beaucoup plus courtes (grâce au choix opportuniste d'un langage à la syntaxe dépouillée), et aussi plus simples grâce à l'abandon du concept de variable modifiable; je n'ai pas besoin de différencier le calcul de la valeur et son retour.
Je suis d'accord et je ne suis pas pour utiliser la paresse par défaut à outrance. J'ai écrit mes exemples en Haskell parce que la légèreté syntaxique permet d'illustrer mon point très efficacement, mais dans la vraie vie j'utilise plutôt OCaml, qui est un langage strict par défaut.
Par ailleurs il y a des interactions intéressantes entre la paresse et la récursivité : en Haskell on ne recherche pas la récursivité terminale, et elle est même néfaste aux performances en général. Je te laisse creuser le sujet si ça t'intéresse, mais dans tous les cas les fonctions écrites ci-dessus sont strictes de toute façon, donc elles ne seront pas évaluées de façon paresseuse (le compilateur s'en rend bien compte).
Pour le fun, une version de fibonacci qui a la bonne complexité et utilise à fond l'évaluation paresseuse (c'est fun et ça fait un bon exercice de gymnastique intellectuelle, mais je ne recommande pas d'écrire comme ça):
(
zipWith (+)envoie la liste des sommes terme-à-terme de deux listes de même taille, ettailrenvoie la liste privée de son premier élément.(!!)est l'accès au n-ième élément d'une liste).