Différence entre la récursion de queue et la récursion structurelle

8

Y a-t-il une différence entre la récursion structurelle et la récursion de queue ou les deux sont-ils identiques? Je vois que dans ces deux récursions, la fonction récursive est appelée sur le sous-ensemble des éléments d'origine.

Khan Saab
la source
7
Quelles recherches avez-vous faites? Quelle est votre compréhension de la récursivité de la queue? Pourquoi pensez-vous que c'est similaire à la récursivité structurelle?
Jonathan Cast
Y a-t-il une chance que vous puissiez ajouter ce que vous avez vu qui vous a fait penser qu'ils pourraient être identiques ou très similaires? Cela pourrait aider les instructeurs à le clarifier lors de l'enseignement des concepts aux gens.
user541686

Réponses:

28
  1. Récursion structurelle: des appels récursifs sont effectués sur des arguments structurellement plus petits .

  2. Récursivité de la queue: l'appel récursif est la dernière chose qui se passe.

Il n'est pas nécessaire que la récursivité de queue soit appelée sur un argument plus petit. En fait, les fonctions récursives de queue sont souvent conçues pour boucler pour toujours. Par exemple, voici une récursion de queue triviale (pas très utile, mais c'est une récursion de queue):

def f(x):
   return f(x+1)

Nous devons en fait être un peu plus prudents. Il peut y avoir plusieurs appels récursifs dans une fonction, et tous n'ont pas besoin d'être récursifs de queue:

def g(x):
  if x < 0:
    return 42             # no recursive call
  elif x < 20:
     return 2 + g(x - 2)  # not tail recursive (must add 2 after the call)
  else:
     return g(x - 3)     # tail recursive

On parle d' appels récursifs de queue . Une fonction dont les appels récursifs sont tous récursifs de queue est alors appelée fonction récursive de queue.

Andrej Bauer
la source
Continuons cette discussion dans le chat .
Davislor
Les commentaires ne sont pas pour une discussion approfondie; cette conversation a été déplacée vers le chat .
DW
-1

La récursivité de queue est un cas très simple de récursion structurelle, où la structure en question est une liste chaînée . Dans la langue que vous utilisez probablement principalement, cette liste n'est probablement pas littéralement dans le code; il s'agit plutôt d'une "liste d'appels à la fonction" conceptuelle, un concept qui peut ne pas être possible d'exprimer tel qu'il est écrit en utilisant ce langage. Dans Haskell (mon langage), tout appel de fonction récursif de queue peut en fait être remplacé par des actions de séquencement sur une liste littérale dont les éléments sont littéralement des "appels à une fonction", mais c'est probablement une chose de langage fonctionnel.

La récursion structurelle est une façon de fonctionner sur un objet défini comme un composite d'autres objets (éventuellement composites). Par exemple, un arbre binaire est un objet contenant des références à deux arbres binaires, ou est vide (il s'agit donc d'un objet défini récursivement ). Moins autoréférentiellement, une paire (t1, t2) contenant deux valeurs de certains types t1 et t2 admet une récursivité structurelle, bien que t1 et t2 n'aient pas besoin également d'être des paires. Cette récursivité prend la forme

action sur la paire = combinaison des résultats d'autres actions sur chaque élément

ce qui ne semble pas très profond.

Il arrive souvent qu'une récursion structurelle ne puisse pas être récursive de queue, bien que toute sorte de récursivité puisse être réécrite comme une récursion de queue (preuve: si vous exécutez la récursivité d'origine, les actions sont terminées dans un certain ordre; par conséquent, la récursivité équivaut à effectuer cette séquence particulière d’actions, qui, comme je l’ai expliqué plus haut, est la récursivité de la queue).

L'arbre binaire ou l'exemple de paire ci-dessus le démontrent: quelle que soit l'organisation des appels récursifs sur les sous-objets, un seul d'entre eux peut être la dernière action; peut-être que ni l'un ni l'autre ne l'est, si leurs résultats sont combinés d'une certaine manière (disons, addition). Comme Andrej Bauer le dit dans sa réponse, cela peut se produire même avec un seul appel récursif, tant que le résultat est modifié. En d'autres termes, pour chaque type d'objet autre que ceux qui sont effectivement des listes liées (un seul sous-objet tout en bas), la récursivité structurelle n'est pas une récursion de queue.

Ryan Reich
la source
1
Il est faux que la récursivité de la queue ne concerne que les listes, imaginées ou réelles. Il est parfaitement possible d'avoir une récursivité de queue sur des arbres binaires, par exemple. Je peux voir pourquoi quelqu'un penserait que c'est parce que le reste de la liste est sa "queue".
Andrej Bauer
@AndrejBauer Je serai convenablement gêné à ce sujet lorsque je comprendrai exactement ce qui ne va pas. Il semble tautologique que la récursivité de queue de la forme f x = (stuff defining x'); f x'soit la même que le séquençage des nœuds dans une liste chaînée définie comme l = modify f : l(dans le style de monade d'état de Haskell). Ce n'était pas seulement la similitude terminologique pour moi. Quant à la récursivité de la queue sur les arbres binaires, pourriez-vous élaborer? Je ne peux que penser au fait de la linéarisation de mon avant-dernier paragraphe.
Ryan Reich
Tous les appels récursifs de queue ne sont pas de cette forme. Par exemple, il peut y avoir plusieurs appels récursifs de queue dans différentes branches (des déclarations de cas, ou quelques-unes). Il est plus naturel de penser à ceux-ci comme sélectionnant un chemin à travers un arbre . Notez également que les appels sont récursifs de queue (ou non), vous pouvez donc avoir f (f x)où l'appel externe de fest récursif de queue. Comment cela s'inscrit-il dans l'idée qu'il s'agit de listes? Voici un autre exemple: f : (Int -> Int) -> (Int -> Int)avec f g 0 = g 42et f g (n + 1) = f (f . g) n. Les possibilités sont infinies et certaines sont utiles.
Andrej Bauer
@AndrejBauer La question portait sur la récursivité de la queue plutôt que sur les seuls appels de queue , donc je ne considérerais pas l' f (f x)applicable: dans l'évaluation du f extérieur, l'intérieur n'est pas un appel de queue (sauf si f est l'identité). Si-déclarations peuvent être trivialement réécrites pas branche dans l'appel de la queue: if (c) then f a else f b == let x = if (c) then a else b in f x. Le dernier exemple n'est pas valide car f . gne vérifie pas le type; même ainsi, ce ne serait pas encore une récursivité de queue: ce f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)n'est pas un appel à f, mais un lambda vraiment différent. (Suivant)
Ryan Reich
Je dirais en fait que cet exemple est celui de la récurrence mutuelle de la queue , c'est-à-dire f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }, mais si vous apportez cela dans la discussion, alors toute fonction récursive, queue ou non, est admissible et le terme perd tout son sens.
Ryan Reich