Is there any difference between structural-recursion and Tail-recursion or they both are same? I see that in both of these recursions , the recursive function is called on the subset of the orignal items.
-
7What research did you do? What is your understanding of tail recursion? Why do you think it's similar to structural recursion? – Jonathan Cast Dec 26 '17 at 14:25
-
Any chance you could add what you saw that made you think they might be same or very similar? It might help instructors clarify it when teaching the concepts to people. – user541686 Dec 27 '17 at 00:31
2 Answers
Structural recursion: recursive calls are made on structurally smaller arguments.
Tail recursion: the recursive call is the last thing that happens.
There is no requirement that the tail recursion should be called on a smaller argument. In fact, quite often tail recursive functions are designed to loop forever. For example, here's a trivial tail recursion (not very useful, but it is tail recursion):
def f(x):
return f(x+1)
We actually have to be a bit more careful. There may be several recursive calls in a function, and not all of them need to be tail recursive:
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
One speaks of tail recursive calls. A function whose recursive calls are all tail-recursive is then called a tail-recursive function.

- 30,396
- 1
- 70
- 117
-
-
Comments are not for extended discussion; this conversation has been moved to chat. – D.W. Dec 30 '17 at 06:18
Tail recursion is a very simple case of structural recursion, where the structure in question is a linked list. In the language you are probably using primarily, this list is probably not literally in the code; rather, it is a conceptual "list of calls to the function", a concept that may not be possible to express as written using that language. In Haskell (my language), any tail-recursive function call can actually be replaced by sequencing actions on a literal list whose elements literally are "calls to a function", but this is probably a functional-language thing.
Structural recursion is a way of operating on an object defined as a composite of other (possibly composite) objects. For example, a binary tree is an object containing references to two binary trees, or is empty (thus, it is a recursively defined object). Less self-referentially, a pair (t1, t2) containing two values of some types t1 and t2 admits structural recursion, although t1 and t2 need not also be pairs. This recursion takes the form
action on the pair = combination of the results of other actions on each element
which doesn't sound very profound.
It is often the case that a structural recursion cannot be tail-recursive, although any kind of recursion can be rewritten as a tail recursion (proof: if you run the original recursion, the actions are completed in a certain order; therefore, the recursion is equivalent to performing that particular sequence of actions, which as I discussed earlier, is tail recursion).
Either the binary tree or the pair example above demonstrate this: however you arrange the recursive calls on the subobjects, only one of them can be the last action; possibly neither one is, if their results are combined in some way (say, addition). As Andrej Bauer says in his answer, this can happen even with only one recursive call, as long as the result is modified. In other words, for every type of object other than those that are effectively linked lists (only one subobject all the way down), structural recursion is not tail recursion.

- 107
- 2
-
1It is false that tail recursion is only about lists, imagined or real. It is perfectly possible to have tail recursion over binary trees, for example. I can see why someone would think it is because the rest of the list is its "tail". – Andrej Bauer Dec 27 '17 at 13:46
-
@AndrejBauer I will feel suitably embarrassed about this when I understand exactly what's wrong. It seems tautological that tail recursion of the form
f x = (stuff defining x'); f x'
is the same as the sequencing of nodes in a linked list defined likel = modify f : l
(in Haskell state-monad style). It wasn't just the terminological similarity for me. As for tail recursion over binary trees, could you elaborate? I can only think of the linearization fact from my second-last paragraph. – Ryan Reich Dec 27 '17 at 23:31 -
Not all tail-recursive calls are of that form. For instance, there can be several tail recursive calls in different branches (of case statements, or some such). It's more natural to think of those as selecting a path through a tree. Also note that the calls are tail recursive (or not), so you could have
f (f x)
where the outer call off
is tail-recursive. How does that fit into the view that it's all about lists? Here's another example:f : (Int -> Int) -> (Int -> Int)
withf g 0 = g 42
andf g (n + 1) = f (f . g) n
. The possibilities are endless, and some are useful. – Andrej Bauer Dec 27 '17 at 23:58 -
@AndrejBauer The question was about tail recursion rather than just tail calls, so I would not consider the
f (f x)
applicable: in the evaluation of the outer f, the inner one is not a tail call (unless f is the identity). If-statements can be trivially rewritten not to branch in the tail call:if (c) then f a else f b == let x = if (c) then a else b in f x
. The last example is invalid becausef . g
doesn't typecheck; even so, it would still not be tail recursion:f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)
is not a call tof
, but a genuinely different lambda. (next) – Ryan Reich Dec 28 '17 at 05:50 -
I'd actually say that example is of mutual tail recursion, i.e.
f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }
, but if you bring that into the discussion, then any recursive function, tail or not, is admissible and the term becomes meaningless. – Ryan Reich Dec 28 '17 at 06:12 -
@RyanReich cf. a tail-recursive
samefringe
for trees here. It manipulates (rotates) the trees themselves, but could instead build log-sized stacks (paths) for each. – Will Ness Dec 28 '17 at 19:26 -
@WillNess Thanks for the link. You are talking about the
gopher
function? It is definitely tail-recursive, yes;samefringe
itself is not, because the expansion ofsame
performs anand
after the call tosamefringe
. I'm not sure what you are suggesting about building paths that would demonstrate a tail-recursive function that isn't evaluated sequentially. I am not saying that a tail-recursive function must be constant-memory; just that the function call stack (potentially) not grow due to (potential) TCO. – Ryan Reich Dec 28 '17 at 21:29 -
@RyanReich no, Lisp's
and
is short-circuiting, and its last form is in tail position. The first call togopher
insamefringe
is not in tail position, but it is of limited, O(tree-height) size (counting the invocations) so it's not important, and could also be re-coded anyway. -- I'm just saying,gopher
changes ("mutates" in Lisp parlance) the tree, but it isn't really needed, we can leave crumbs - unexplored branches, to be explored later, - behind us, (i.e. in an additional argument, used in FIFO manner), as we descend each tree towards its first leaf. – Will Ness Dec 29 '17 at 00:24 -
when you say "structural recursion cannot be tail-recursive" but "can be written as tail-recursion", do you mean for the former what SICP calls an iterative process, i.e. with O(1) call stack usage? Because otherwise it is very confusing, the two terms are nearly identical and it is not clear what you mean there; it reads as self-contradiction. I think SICP's "iterative" vs. "essentially recursive" terminology is preferable, is much clearer. It is entirely possible, as is well known, to code anything as tail-recursion with CPS, so what you say re: trees is not a counterexample. – Will Ness Dec 29 '17 at 01:04
-
For example, quicksort too has two branches, but can be coded linearly easily, see e.g. this. In Haskell everything is delayed anyway, and it gives us a kind of CPS for free. But it can be coded in strict languages just the same, building a "to-do list" and working on those postponed parts when we get to them. – Will Ness Dec 29 '17 at 01:04
-
@WillNess I still don't understand the point of this analysis of samefringe, and to investigate further would require me to guess the kind of code that would demonstrate tail recursion in a structural recursive algorithm on a tree. --- A better way of phrasing the comparison is that the result of a structurally recursive function can also be realized as the result of a tail-recursive function (which may, as
gopher
, operate on the non-linear structure, but not in a structurally recursive way). This is of course the fact you call "well-known", which I also mentioned in my answer. – Ryan Reich Dec 30 '17 at 06:47 -
I think our problem here is ambiguity over my use of the term "structural recursion". My prototype is
data T a = N | T (T a) (T a); f (T l r) = g (f l) (f r); f N = x0
for some combining functiong
and initial valuex0
. This is compatible with, say, SICP's description of "tree recursion" (section 1.2.2, also 2.2.2). I don't mean a function likef' (T l r) = h (k l) r
for some functionsh
andk
. This is how CPS looks, for instance. Perhaps I am wrong and structural recursion has essentially different examples. – Ryan Reich Dec 30 '17 at 07:04 -
this is what I had in mind (it is buggy, but you'll get the gist). I just meant that a recursion over trees can be coded in tail-recursive manner. In general the transformed code will be very far from the nice doubly-recursive original, but implementing the same algorithm nevertheless. Perhaps it indeed is just a question of terminology - I always thought "structural recursion" applies to algorithms+data, "tail recursion" applies to code. – Will Ness Dec 30 '17 at 19:17
-