La récursivité est-elle une fonctionnalité en soi?

116

... ou est-ce juste une pratique?

Je pose cette question à cause d'une dispute avec mon professeur: j'ai perdu le crédit pour appeler une fonction de manière récursive sur la base que nous n'avons pas couvert la récursivité en classe, et mon argument est que nous l'avons appris implicitement par l'apprentissage returnet les méthodes.

Je demande ici parce que je soupçonne que quelqu'un a une réponse définitive.

Par exemple, quelle est la différence entre les deux méthodes suivantes:

public static void a() {
    return a();
    }

public static void b() {
    return a();
    }

À part " acontinue pour toujours" (dans le programme réel, il est utilisé correctement pour inviter à nouveau un utilisateur lorsqu'il est fourni avec une entrée invalide), y a-t-il une différence fondamentale entre aet b? Pour un compilateur non optimisé, comment sont-ils gérés différemment?

Au bout du compte , il se résume à savoir si en apprenant à return a()partir bque nous à cet effet aussi appris à return a()partir a. Avons-nous?

Áxel Costas Pena
la source
24
Excellent débat. Je me demande si vous l'avez expliqué ainsi à votre professeur. Si vous l'avez fait, je pense qu'il devrait vous donner le crédit que vous avez perdu.
Michael Yaworski
57
La récursivité n'est même pas un concept exclusif à l'informatique. La fonction de Fibonacci, l'opérateur factoriel et beaucoup d'autres choses issues des mathématiques (et éventuellement d'autres champs) sont (ou du moins peuvent être) exprimées de manière récursive. Le professeur exige-t-il que vous soyez également inconscient de ces choses?
Theodoros Chatzigiannakis
34
Le professeur devrait plutôt lui donner un crédit supplémentaire, pour avoir proposé une manière élégante de résoudre un problème, ou pour avoir pensé sortir des sentiers battus.
11
Quelle était la mission? C'est un problème sur lequel je me suis souvent demandé, lorsque vous soumettez une mission de programmation, ce qui est marqué, votre capacité à résoudre un problème ou votre capacité à utiliser ce que vous avez appris. Ces deux ne sont pas nécessairement les mêmes.
Leon
35
FWIW, demander une entrée jusqu'à ce que ce soit correct n'est pas un bon endroit pour utiliser la récursivité, il est trop facile de déborder la pile. Pour ce cas particulier, il serait préférable d'utiliser quelque chose comme a() { do { good = prompt(); } while (!good); }.
Kevin

Réponses:

113

Pour répondre à votre question spécifique: Non, du point de vue de l'apprentissage d'une langue, la récursivité n'est pas une fonctionnalité. Si votre professeur vous a vraiment marqué pour avoir utilisé une "fonction" qu'il n'avait pas encore enseignée, c'était faux.

En lisant entre les lignes, une possibilité est qu'en utilisant la récursivité, vous avez évité d'utiliser une fonctionnalité qui était censée être un résultat d'apprentissage pour son cours. Par exemple, vous n'avez peut-être pas du tout utilisé l'itération, ou peut-être n'avez-vous utilisé que des forboucles au lieu d'utiliser à la fois foret while. Il est courant qu'un devoir vise à tester votre capacité à faire certaines choses, et si vous évitez de les faire, votre professeur ne peut tout simplement pas vous accorder les notes réservées à cette fonctionnalité. Cependant, si cela était vraiment la cause de vos notes perdues, le professeur devrait considérer cela comme une expérience d'apprentissage qui lui est propre - si la démonstration de certains résultats d'apprentissage est l'un des critères d'une tâche, cela devrait être clairement expliqué aux étudiants. .

Cela dit, je suis d'accord avec la plupart des autres commentaires et réponses que l'itération est un meilleur choix que la récursivité ici. Il y a plusieurs raisons, et bien que d'autres personnes les aient abordées dans une certaine mesure, je ne suis pas sûr qu'elles aient pleinement expliqué la pensée qui les sous-tend.

Dépassements de pile

Le plus évident est que vous risquez d'obtenir une erreur de débordement de pile. En réalité, il est très peu probable que la méthode que vous avez écrite en mène à une, car un utilisateur devrait donner une entrée incorrecte plusieurs fois pour déclencher un débordement de pile.

Cependant, une chose à garder à l'esprit est que non seulement la méthode elle-même, mais d'autres méthodes supérieures ou inférieures dans la chaîne d'appels seront sur la pile. Pour cette raison, engloutir avec désinvolture l'espace disponible dans la pile est une chose assez impolie pour toute méthode. Personne ne veut avoir à s'inquiéter constamment de l'espace libre dans la pile à chaque fois qu'il écrit du code en raison du risque que d'autres codes en aient inutilement utilisé une grande partie.

Cela fait partie d'un principe plus général de la conception de logiciels appelé abstraction. Essentiellement, lorsque vous appelez DoThing(), tout ce dont vous devriez vous soucier, c'est que la chose est faite. Vous ne devriez pas avoir à vous soucier des détails de mise en œuvre de la façon dont cela est fait. Mais une utilisation gourmande de la pile rompt ce principe, car chaque bit de code doit s'inquiéter de la quantité de pile qu'il peut supposer en toute sécurité qu'il lui a laissé par le code ailleurs dans la chaîne d'appels.

Lisibilité

L'autre raison est la lisibilité. L'idéal auquel le code devrait aspirer est d'être un document lisible par l'homme, où chaque ligne décrit simplement ce qu'il fait. Adoptez ces deux approches:

private int getInput() {
    int input;
    do {
        input = promptForInput();
    } while (!inputIsValid(input))
    return input;
}

contre

private int getInput() {
    int input = promptForInput();
    if(inputIsValid(input)) {
        return input;
    }
    return getInput();
}

Oui, ces deux méthodes fonctionnent, et oui, elles sont toutes les deux assez faciles à comprendre. Mais comment les deux approches pourraient-elles être décrites en anglais? Je pense que ce serait quelque chose comme:

Je vais demander une entrée jusqu'à ce que l'entrée soit valide, puis la retourner

contre

Je vais demander une entrée, puis si l'entrée est valide, je la retournerai, sinon j'obtiens l'entrée et je renvoie le résultat à la place

Peut-être pouvez-vous penser à un libellé un peu moins maladroit pour ce dernier, mais je pense que vous constaterez toujours que le premier sera une description plus précise, conceptuellement, de ce que vous essayez réellement de faire. Cela ne veut pas dire que la récursivité est toujours moins lisible. Pour les situations où cela brille, comme la traversée d'arbres, vous pouvez faire le même type d'analyse côte à côte entre la récursivité et une autre approche et vous trouverez presque certainement que la récursivité donne un code qui se décrit plus clairement, ligne par ligne.

Isolés, ces deux éléments sont de petits points. Il est très peu probable que cela conduise vraiment à un débordement de pile, et le gain de lisibilité est mineur. Mais tout programme sera une collection de bon nombre de ces petites décisions, donc même si isolément elles n'ont pas beaucoup d'importance, il est important d'apprendre les principes pour les faire correctement.

Ben Aaronson
la source
8
Pouvez-vous développer votre affirmation selon laquelle la récursivité n'est pas une fonctionnalité? J'ai soutenu dans ma réponse que oui, parce que tous les compilateurs ne le supportent pas nécessairement.
Harry Johnston
5
Tous les langages ne supportent pas nécessairement la récursivité non plus, donc ce n'est pas nécessairement juste une question de choisir le bon compilateur - mais vous avez tout à fait raison de dire que «fonctionnalité» est une description intrinsèquement ambiguë, donc assez juste. Votre deuxième point est également juste du point de vue de quelqu'un qui apprend à programmer (comme c'est maintenant habituel) sans avoir d'abord une expérience en programmation de code machine. :-)
Harry Johnston
2
Notez que le problème de «lisibilité» est un problème de syntaxe. Il n'y a rien de fondamentalement "illisible" dans la récursivité. En fait, l'induction est le moyen le plus simple d'exprimer des structures de données inductives, telles que des boucles, des listes et des séquences, etc. Et la plupart des structures de données sont inductives.
nomen
6
Je pense que vous avez empilé le jeu avec votre libellé. Vous avez décrit la version itérative de manière fonctionnelle et vice-versa. Je pense qu'une description plus juste ligne par ligne des deux serait «Je vais demander des commentaires. Si l'entrée n'est pas valide, je continuerai à répéter l'invite jusqu'à ce que j'obtienne une entrée valide. Ensuite, je vous le rendrai. vs «Je vais demander une entrée. Si l'entrée est valide, je la retournerai. Sinon, je retournerai le résultat d'une reprise. » (Mes enfants ont compris le concept fonctionnel d'un do-over quand ils étaient à l'école maternelle, donc je pense que c'est un résumé anglais légitime du concept récursif ici.)
pjs
2
@HarryJohnston Le fait de ne pas prendre en charge la récursivité serait une exception aux fonctionnalités existantes plutôt qu'un manque de nouvelle fonctionnalité. En particulier, dans le contexte de cette question, une "nouvelle fonctionnalité" signifie "un comportement utile que nous n'avons pas encore enseigné existe", ce qui n'est pas vrai de la récursivité car c'est une extension logique des fonctionnalités qui ont été enseignées (à savoir, que les procédures contiennent instructions et les appels de procédure sont des instructions). C'est comme si le professeur enseignait l'ajout d'un étudiant, puis le réprimandait pour avoir ajouté la même valeur plus d'une fois parce que "nous n'avons pas couvert la multiplication".
nmclean
48

Pour répondre à la question littérale, plutôt qu'à la méta-question: la récursivité est une fonctionnalité, dans le sens où tous les compilateurs et / ou langages ne le permettent pas nécessairement. En pratique, il est attendu de tous les compilateurs modernes (ordinaires) - et certainement de tous les compilateurs Java! - mais ce n'est pas universellement vrai.

Comme exemple artificiel de la raison pour laquelle la récursivité peut ne pas être prise en charge, considérez un compilateur qui stocke l'adresse de retour d'une fonction dans un emplacement statique; cela peut être le cas, par exemple, pour un compilateur pour un microprocesseur qui n'a pas de pile.

Pour un tel compilateur, lorsque vous appelez une fonction comme celle-ci

a();

il est implémenté comme

move the address of label 1 to variable return_from_a
jump to label function_a
label 1

et la définition de (),

function a()
{
   var1 = 5;
   return;
}

est implémenté comme

label function_a
move 5 to variable var1
jump to the address stored in variable return_from_a

Espérons que le problème lorsque vous essayez d'appeler a()récursivement dans un tel compilateur est évident; le compilateur ne sait plus comment revenir de l'appel externe, car l'adresse de retour a été écrasée.

Pour le compilateur que j'ai réellement utilisé (fin des années 70 ou début des années 80, je pense) sans prise en charge de la récursivité, le problème était légèrement plus subtil que cela: l'adresse de retour serait stockée sur la pile, comme dans les compilateurs modernes, mais les variables locales n'étaient pas 't. (Théoriquement, cela devrait signifier que la récursivité était possible pour les fonctions sans variables locales non statiques, mais je ne me souviens pas si le compilateur l'a explicitement pris en charge ou non. Il a peut-être eu besoin de variables locales implicites pour une raison quelconque.)

En regardant vers l'avenir, je peux imaginer des scénarios spécialisés - des systèmes fortement parallèles, peut-être - où ne pas avoir à fournir une pile pour chaque thread pourrait être avantageux, et où la récursivité n'est donc autorisée que si le compilateur peut la refactoriser en boucle. (Bien sûr, les compilateurs primitifs dont j'ai parlé ci-dessus n'étaient pas capables de tâches compliquées telles que la refactorisation du code.)

Harry Johnston
la source
Par exemple, le préprocesseur C ne prend pas en charge la récursivité dans les macros. Les définitions de macros se comportent de la même manière que les fonctions, mais vous ne pouvez pas les appeler de manière récursive.
STH
7
Votre «exemple artificiel» n'est pas tout à fait artificiel: le standard Fortran 77 ne permettait pas aux fonctions de s'appeler récursivement - la raison étant à peu près ce que vous décrivez. (Je crois que l'adresse à laquelle passer lorsque la fonction a été effectuée était stockée à la fin du code de fonction lui-même, ou quelque chose d'équivalent à cet arrangement.) Voir ici pour un peu plus à ce sujet.
alexis
5
Les langages Shader ou les langages GPGPU (par exemple, GLSL, Cg, OpenCL C) ne prennent pas en charge la récursivité, par exemple. Dans la mesure où, l'argument "toutes les langues ne le supportent pas" est certainement valable. La récursivité suppose l'équivalent d'une pile (ce n'est pas nécessairement une pile , mais il doit y avoir un moyen de stocker les adresses de retour et les cadres de fonction d'une manière ou d'une autre ).
Damon
Un compilateur Fortran sur lequel j'ai travaillé un peu au début des années 1970 n'avait pas de pile d'appels. Chaque sous-programme ou fonction avait des zones de mémoire statique pour l'adresse de retour, les paramètres et ses propres variables.
Patricia Shanahan
2
Même certaines versions de Turbo Pascal désactivaient la récursivité par défaut, et vous deviez définir une directive de compilation pour l'activer.
dan04
17

L'enseignant veut savoir si vous avez étudié ou non. Apparemment, vous n'avez pas résolu le problème de la manière qu'il vous a enseignée ( la bonne façon ; itération), et par conséquent, vous considérez que vous ne l'avez pas fait. Je suis tout à fait pour les solutions créatives, mais dans ce cas, je dois être d'accord avec votre enseignant pour une raison différente:
si l'utilisateur fournit une entrée invalide trop de fois (c'est-à-dire en maintenant la touche Entrée enfoncée), vous aurez une exception de dépassement de capacité de pile et votre la solution plantera. De plus, la solution itérative est plus efficace et plus facile à entretenir. Je pense que c'est la raison pour laquelle votre professeur aurait dû vous donner.

Mike
la source
2
On ne nous a pas dit d'accomplir cette tâche d'une manière spécifique; et nous avons appris les méthodes, pas seulement l'itération. De plus, je laisserais celui qui est le plus facile à lire selon mes préférences personnelles: j'ai choisi ce qui me semblait bon. L'erreur SO est nouvelle pour moi, bien que l'idée que la récursion soit une caractéristique en soi ne semble toujours pas fondée.
3
"Je laisserais celui qui est le plus facile à lire selon mes préférences personnelles". D'accord. La récursivité n'est pas une fonctionnalité Java. Ce sont.
mike
2
@Vality: Élimination des appels de queue? Certaines machines virtuelles Java peuvent le faire, mais gardez à l'esprit qu'elle doit également conserver une trace de pile pour les exceptions. Si elle permet l'élimination des appels de queue, la trace de pile, générée naïvement, peut devenir invalide, de sorte que certaines JVM ne font pas TCE pour cette raison.
icktoofay
5
Quoi qu'il en soit, compter sur l'optimisation pour rendre votre code cassé moins cassé est une forme plutôt mauvaise.
cHao
7
+1, voyez que dans Ubuntu récemment, l'écran de connexion a été cassé lorsque l'utilisateur a
Sebastian
13

Déduire des points parce que "nous n'avons pas couvert la récursivité en classe" est horrible. Si vous avez appris à appeler la fonction A qui appelle la fonction B qui appelle la fonction C qui retourne à B qui revient à A qui revient à l'appelant, et que l'enseignant ne vous a pas dit explicitement que ce doivent être des fonctions différentes (qui serait le cas dans les anciennes versions de FORTRAN, par exemple), il n'y a aucune raison pour que A, B et C ne puissent pas tous être la même fonction.

D'un autre côté, nous devrons voir le code réel pour décider si, dans votre cas particulier, l'utilisation de la récursivité est vraiment la bonne chose à faire. Il n'y a pas beaucoup de détails, mais cela semble faux.

gnasher729
la source
10

Il y a de nombreux points de vue à examiner concernant la question spécifique que vous avez posée, mais ce que je peux dire, c'est que du point de vue de l'apprentissage d'une langue, la récursivité n'est pas une fonctionnalité en soi. Si votre professeur vous a vraiment marqué pour avoir utilisé une "fonction" qu'il n'avait pas encore enseignée, c'était faux, mais comme je l'ai dit, il y a d'autres points de vue à considérer ici qui font en fait que le professeur a raison lorsqu'il déduit des points.

D'après ce que je peux déduire de votre question, utiliser une fonction récursive pour demander une entrée en cas d'échec d'entrée n'est pas une bonne pratique puisque chaque appel de fonctions récursives est poussé vers la pile. Étant donné que cette récursivité est pilotée par l'entrée de l'utilisateur, il est possible d'avoir une fonction récursive infinie et donc un StackOverflow.

Il n'y a aucune différence entre ces 2 exemples que vous avez mentionnés dans votre question dans le sens de ce qu'ils font (mais diffèrent par d'autres moyens) - Dans les deux cas, une adresse de retour et toutes les informations de méthode sont chargées dans la pile. Dans un cas de récursivité, l'adresse de retour est simplement la ligne juste après l'appel de la méthode (bien sûr, ce n'est pas exactement ce que vous voyez dans le code lui-même, mais plutôt dans le code créé par le compilateur). En Java, C et Python, la récursivité est assez chère par rapport à l'itération (en général) car elle nécessite l'allocation d'un nouveau frame de pile. Sans oublier que vous pouvez obtenir une exception de dépassement de capacité de pile si l'entrée n'est pas valide trop de fois.

Je crois que le professeur a déduit des points car la récursivité est considérée comme un sujet à part entière et il est peu probable que quelqu'un sans expérience en programmation pense à la récursivité. (Bien sûr, cela ne signifie pas qu'ils ne le feront pas, mais c'est peu probable).

IMHO, je pense que le professeur a raison en vous déduisant les points. Vous auriez pu facilement utiliser la partie validation dans une autre méthode et l'utiliser comme ceci:

public bool foo() 
{
  validInput = GetInput();
  while(!validInput)
  {
    MessageBox.Show("Wrong Input, please try again!");
    validInput = GetInput();
  }
  return hasWon(x, y, piece);
}

Si ce que vous avez fait peut effectivement être résolu de cette manière, alors ce que vous avez fait était une mauvaise pratique et devrait être évité.

Yonatan Nir
la source
Le but de la méthode elle-même est de valider l'entrée, puis d'appeler et de renvoyer le résultat d' une autre méthode (c'est pourquoi elle se retourne). Pour être précis, il vérifie si le coup dans un jeu Tic-Tac-Toe est valide, puis revient hasWon(x, y, piece)(pour vérifier uniquement la ligne et la colonne affectées).
vous pouvez facilement prendre la partie validation UNIQUEMENT et la mettre dans une autre méthode appelée "GetInput" par exemple, puis l'utiliser comme je l'ai écrit dans ma réponse. J'ai édité ma réponse avec à quoi elle devrait ressembler. Bien sûr, vous pouvez faire en sorte que GetInput renvoie un type contenant les informations dont vous avez besoin.
Yonatan Nir
1
Yonatan Nir: Quand la récursivité était-elle une mauvaise pratique? Peut-être que JVM va exploser parce que la VM Hotspot n'a pas pu optimiser en raison de la sécurité du code d'octet et d'autres choses seraient un bon argument. En quoi votre code est-il différent si ce n'est qu'il utilise une approche différente?
1
La récursivité n'est pas toujours une mauvaise pratique, mais si elle peut être évitée et garder le code propre et facile à maintenir, elle doit être évitée. En Java, C et Python, la récursivité est assez chère par rapport à l'itération (en général) car elle nécessite l'allocation d'un nouveau frame de pile. Dans certains compilateurs C, on peut utiliser un indicateur de compilateur pour éliminer cette surcharge, qui transforme certains types de récursion (en fait, certains types d'appels de queue) en sauts au lieu d'appels de fonction.
Yonatan Nir
1
Ce n'est pas clair, mais si vous avez remplacé une boucle avec un nombre indéfini d'itérations par récursivité, alors c'est mauvais. Java ne garantit pas l'optimisation des appels de fin, vous pouvez donc facilement manquer d'espace de pile. En Java, n'utilisez pas la récursivité, sauf si vous êtes assuré d'avoir un nombre limité d'itérations (généralement logarithmique par rapport à la taille totale des données).
hyde
6

Peut-être que votre professeur ne l'a pas encore enseigné, mais il semble que vous soyez prêt à apprendre les avantages et les inconvénients de la récursivité.

Le principal avantage de la récursivité est que les algorithmes récursifs sont souvent beaucoup plus faciles et plus rapides à écrire.

Le principal inconvénient de la récursivité est que les algorithmes récursifs peuvent provoquer des débordements de pile, car chaque niveau de récursivité nécessite l'ajout d'un frame de pile supplémentaire à la pile.

Pour le code de production, où la mise à l'échelle peut entraîner beaucoup plus de niveaux de récursivité en production que dans les tests unitaires du programmeur, l'inconvénient l'emporte généralement sur l'avantage, et le code récursif est souvent évité lorsque cela est pratique.

Warren Dew
la source
1
Tout algorithme récursif potentiellement risqué peut toujours être réécrit de manière triviale pour utiliser une pile explicite - la pile d'appels n'est, après tout, qu'une pile. Dans ce cas, si vous réécrivez la solution pour utiliser une pile, cela semblerait ridicule - une preuve supplémentaire que la réponse récursive n'est pas très bonne.
Aaronaught
1
Si les débordements de pile sont un problème, vous devez utiliser un langage / runtime qui prend en charge l'optimisation des appels de queue, tel que .NET 4.0 ou tout autre langage de programmation fonctionnel
Sebastian
Toutes les récursions ne sont pas des appels de queue.
Warren Dew
6

En ce qui concerne la question spécifique, la récursivité est-elle une fonctionnalité, je suis enclin à dire oui, mais après avoir réinterprété la question. Il existe des choix de conception courants de langages et de compilateurs qui rendent la récursivité possible, et il existe des langages complets de Turing qui ne permettent pas du tout la récursivité . En d'autres termes, la récursivité est une capacité qui est activée par certains choix dans la conception de langage / compilateur.

  • La prise en charge des fonctions de première classe rend la récursivité possible sous des hypothèses très minimales; voir l' écriture de boucles dans Unlambda pour un exemple, ou cette expression Python obtuse ne contenant pas d'auto-références, de boucles ou d'assignations:

    >>> map((lambda x: lambda f: x(lambda g: f(lambda v: g(g)(v))))(
    ...   lambda c: c(c))(lambda R: lambda n: 1 if n < 2 else n * R(n - 1)),
    ...   xrange(10))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
    
  • Les langages / compilateurs qui utilisent une liaison tardive , ou qui définissent des déclarations directes , rendent la récursivité possible. Par exemple, alors que Python autorise le code ci-dessous, c'est un choix de conception (liaison tardive), pas une exigence pour un système Turing-complet . Les fonctions mutuellement récursives dépendent souvent de la prise en charge des déclarations directes.

    factorial = lambda n: 1 if n < 2 else n * factorial(n-1)
    
  • Les langages à typage statique qui autorisent les types définis de manière récursive contribuent à activer la récursivité. Voir cette implémentation du Y Combinator dans Go . Sans types définis de manière récursive, il serait toujours possible d'utiliser la récursivité dans Go, mais je pense que le combinateur Y serait spécifiquement impossible.

wberry
la source
1
Cela a fait exploser ma tête, en particulier l'Unlambda +1
John Powell
Les combinateurs à virgule fixe sont difficiles. Quand j'ai décidé d'apprendre la programmation fonctionnelle, je me suis forcé à étudier le combinateur Y jusqu'à ce que je le comprenne, puis à l'appliquer pour écrire d'autres fonctions utiles. Cela m'a pris du temps, mais cela en valait la peine.
wberry
5

D'après ce que je peux déduire de votre question, utiliser une fonction récursive pour demander une entrée en cas d'échec d'entrée n'est pas une bonne pratique. Pourquoi?

Parce que chaque appel de fonctions récursives est poussé vers la pile. Étant donné que cette récursion est entraîné par l' entrée utilisateur , il est possible d'avoir une fonction récursive infinie et entraînant ainsi une StackOverflow :-p

Avoir une boucle non récursive pour ce faire est la voie à suivre.

remudada
la source
L'essentiel de la méthode en question, et le but de la méthode elle-même, est de valider l'entrée par divers contrôles. Le processus recommence si l'entrée est invalide jusqu'à ce que l'entrée soit correcte (comme indiqué).
4
@fay Mais si l'entrée est invalide trop de fois, vous obtiendrez une StackOverflowError. La récursivité est plus élégante, mais à mes yeux, elle pose généralement plus de problème qu'une boucle régulière (en raison des piles).
Michael Yaworski
1
C'est donc un bon point intéressant. Je n'avais pas envisagé cette erreur. Cependant, le même effet peut-il être obtenu en while(true)appelant la même méthode? Si tel est le cas, je ne dirais pas que cela prend en charge une quelconque différence entre la récursivité, bon à savoir tel quel.
1
@fay while(true)est une boucle infinie. Sauf si vous avez une breakdéclaration, je ne vois pas l'intérêt, sauf si vous essayez de planter votre programme lol. Mon point est que si vous appelez la même méthode (c'est la récursivité), cela vous donnera parfois une StackOverflowError , mais si vous utilisez une boucle whileou for, ce ne sera pas le cas. Le problème n'existe tout simplement pas avec une boucle régulière. Peut-être que je vous ai mal compris, mais ma réponse est non.
Michael Yaworski
4
Cela me semble honnêtement être la vraie raison pour laquelle le professeur a pris des notes =) Il ne l'a peut-être pas très bien expliqué, mais c'est une plainte valable de dire que vous l'utilisiez d'une manière qui serait considérée comme un style très médiocre sinon carrément imparfait dans un code plus sérieux.
Commander Coriander Salamander
3

La récursivité est un concept de programmation , une fonctionnalité (comme l'itération) et une pratique . Comme vous pouvez le voir sur le lien, il existe un vaste domaine de recherche dédié au sujet. Peut-être n'avons-nous pas besoin d'approfondir le sujet pour comprendre ces points.

La récursivité comme fonctionnalité

En termes simples, Java le prend en charge implicitement, car il permet à une méthode (qui est fondamentalement une fonction spéciale) d'avoir une "connaissance" d'elle-même et des autres méthodes composant la classe à laquelle elle appartient. Considérez un langage où ce n'est pas le cas: vous seriez capable d'écrire le corps de cette méthode a, mais vous ne seriez pas capable d'inclure un appel adedans. La seule solution serait d'utiliser l'itération pour obtenir le même résultat. Dans un tel langage, il faudrait faire une distinction entre les fonctions conscientes de leur propre existence (en utilisant un jeton de syntaxe spécifique), et celles qui ne le font pas! En fait, tout un groupe de langages fait cette distinction (voir les familles Lisp et ML par exemple). Fait intéressant, Perl autorise même les fonctions anonymes (appeléeslambdas ) pour s'appeler récursivement (encore une fois, avec une syntaxe dédiée).

pas de récursivité?

Pour les langages qui ne supportent même pas la possibilité de récursivité, il existe souvent une autre solution, sous la forme du combinateur à virgule fixe , mais cela nécessite toujours que le langage prenne en charge des fonctions comme des objets dits de première classe (c'est-à-dire des objets qui peuvent être manipulé dans la langue elle-même).

La récursivité comme pratique

Avoir cette fonctionnalité disponible dans une langue ne signifie pas nécessairement qu'elle est idiomatique. Dans Java 8, des expressions lambda ont été incluses, il pourrait donc devenir plus facile d'adopter une approche fonctionnelle de la programmation. Cependant, il y a des considérations pratiques:

  • la syntaxe n'est toujours pas très conviviale pour la récursivité
  • les compilateurs peuvent ne pas être en mesure de détecter cette pratique et de l' optimiser

La ligne du bas

Heureusement (ou plus précisément, pour plus de facilité d'utilisation), Java laisse les méthodes prendre conscience d'elles-mêmes par défaut et prend donc en charge la récursivité, donc ce n'est pas vraiment un problème pratique, mais cela reste encore théorique, et je suppose que votre professeur voulait y répondre spécifiquement. En outre, à la lumière de l'évolution récente de la langue, cela pourrait devenir quelque chose d'important à l'avenir.

didierc
la source