Difference between revisions of "Tail recursion"

From HaskellWiki
Jump to navigation Jump to search
(link to Fold)
m (is locali"z"ation so important?)
(6 intermediate revisions by 4 users not shown)
Line 4: Line 4:
 
1 to it, or consing another element onto the beginning of it), it is
 
1 to it, or consing another element onto the beginning of it), it is
 
not tail recursive.
 
not tail recursive.
Note that <hask>foldl</hask> is always [[Fold|tail recursive]].
 
   
  +
Here is formal definition of "tail recursive". "<code-haskell>f</code-haskell> occurs in <code-haskell>t</code-haskell>" means <code-haskell>f</code-haskell> is a free variable of <code-haskell>t</code-haskell>.
With that said, tail recursion is not that useful of a concept in a
 
lazy language like Haskell. The important concept to know in Haskell
 
is [[guarded recursion]], where any recursive calls occur within a data
 
constructor (such as <hask>foldr</hask>, where the recursive call to foldr occurs
 
as an argument to <hask>(:)</hask>). This allows the result of the function to be
 
consumed lazily, since it can be evaluated up to the data constructor
 
and the recursive call delayed until needed.
 
   
  +
When a function is defined (in <code-haskell>let</code-haskell> or at the top level) as:
== Source ==
 
  +
f = t
  +
where <code-haskell>f</code-haskell> is a name and <code-haskell>t</code-haskell> is a lambda-term, <code-haskell>f</code-haskell> is ''tail recursive'' iff <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t</code-haskell>. <code-haskell>f</code-haskell> ''occurs tail recursively'' in <code-haskell>t</code-haskell> iff <code-haskell>f</code-haskell> occurs in <code-haskell>t</code-haskell> and any of the following holds:
  +
* <code-haskell>t</code-haskell> is variable;
  +
* <code-haskell>t</code-haskell> is "<code-haskell>\var -> t0</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell>;
  +
* <code-haskell>t</code-haskell> is "<code-haskell>t0 t1</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell> and does not occur in <code-haskell>t1</code-haskell>;
  +
* <code-haskell>t</code-haskell> is "<code-haskell>let bs in t0</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell> and for each binder "<code-haskell>var = t1</code-haskell>" in <code-haskell>bs</code-haskell>, <code-haskell>f</code-haskell> does not occur in <code-haskell>t1</code-haskell>;
  +
* <code-haskell>t</code-haskell> is "<code-haskell>case t0 of bs</code-haskell>" and <code-haskell>f</code-haskell> does not occur in <code-haskell>t0</code-haskell> and for each branch <code-haskell>b</code-haskell> in <code-haskell>bs</code-haskell>, <code-haskell>f</code-haskell> does not occur or occurs tail recursively in <code-haskell>b</code-haskell>;
  +
** when we are saying "occur in <code-haskell>b</code-haskell>", <code-haskell>b</code-haskell> has form "<code-haskell>D vars -> t</code-haskell>" (where <code-haskell>D</code-haskell> is some data constructor and <code-haskell>vars</code-haskell> is a sequence of names), we are thinking of the lambda-abstraction "<code-haskell>\vars -> t</code-haskell>" instead of <code-haskell>b</code-haskell>.
  +
 
Note that [[Fold|foldl]] is tail recursive.
  +
  +
The important concept to know in Haskell is [[guarded recursion]] (see [http://en.wikipedia.org/wiki/Tail_recursion#Tail_recursion_modulo_cons tail recursion modulo cons]), where any recursive calls occur within a data constructor (such as <hask>foldr</hask>, where the recursive call to foldr occurs as an argument to <hask>(:)</hask>). This allows the result of the function to be consumed lazily, since it can be evaluated up to the data constructor and the recursive call delayed until needed.
  +
  +
== Tail call optimisation ==
  +
  +
In many programming languages, calling a function uses stack space, so a function that is tail recursive can build up a large stack of calls to itself, which wastes memory. Since in a tail call, the containing function is about to return, its environment can actually be discarded and the recursive call can be entered without creating a new stack frame. This trick is called ''tail call elimination'' or ''tail call optimisation'' and allows tail-recursive functions to recur indefinitely.
  +
  +
In Haskell, the function call model is a little different, function calls might not use a new stack frame, so making a function tail-recursive typically isn't as big a deal—being ''productive'', via guarded recursion, is more usually a concern.
  +
 
== See also ==
   
 
* Brent Yorgey in Haskell-Cafe on [http://www.haskell.org/pipermail/haskell-cafe/2009-March/058607.html Definition of "tail recursive" wrt Folds]
 
* Brent Yorgey in Haskell-Cafe on [http://www.haskell.org/pipermail/haskell-cafe/2009-March/058607.html Definition of "tail recursive" wrt Folds]

Revision as of 20:41, 5 July 2012

A recursive function is tail recursive if the final result of the recursive call is the final result of the function itself. If the result of the recursive call must be further processed (say, by adding 1 to it, or consing another element onto the beginning of it), it is not tail recursive.

Here is formal definition of "tail recursive". "<code-haskell>f</code-haskell> occurs in <code-haskell>t</code-haskell>" means <code-haskell>f</code-haskell> is a free variable of <code-haskell>t</code-haskell>.

When a function is defined (in <code-haskell>let</code-haskell> or at the top level) as:

 f = t

where <code-haskell>f</code-haskell> is a name and <code-haskell>t</code-haskell> is a lambda-term, <code-haskell>f</code-haskell> is tail recursive iff <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t</code-haskell>. <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t</code-haskell> iff <code-haskell>f</code-haskell> occurs in <code-haskell>t</code-haskell> and any of the following holds:

  • <code-haskell>t</code-haskell> is variable;
  • <code-haskell>t</code-haskell> is "<code-haskell>\var -> t0</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell>;
  • <code-haskell>t</code-haskell> is "<code-haskell>t0 t1</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell> and does not occur in <code-haskell>t1</code-haskell>;
  • <code-haskell>t</code-haskell> is "<code-haskell>let bs in t0</code-haskell>" and <code-haskell>f</code-haskell> occurs tail recursively in <code-haskell>t0</code-haskell> and for each binder "<code-haskell>var = t1</code-haskell>" in <code-haskell>bs</code-haskell>, <code-haskell>f</code-haskell> does not occur in <code-haskell>t1</code-haskell>;
  • <code-haskell>t</code-haskell> is "<code-haskell>case t0 of bs</code-haskell>" and <code-haskell>f</code-haskell> does not occur in <code-haskell>t0</code-haskell> and for each branch <code-haskell>b</code-haskell> in <code-haskell>bs</code-haskell>, <code-haskell>f</code-haskell> does not occur or occurs tail recursively in <code-haskell>b</code-haskell>;
    • when we are saying "occur in <code-haskell>b</code-haskell>", <code-haskell>b</code-haskell> has form "<code-haskell>D vars -> t</code-haskell>" (where <code-haskell>D</code-haskell> is some data constructor and <code-haskell>vars</code-haskell> is a sequence of names), we are thinking of the lambda-abstraction "<code-haskell>\vars -> t</code-haskell>" instead of <code-haskell>b</code-haskell>.

Note that foldl is tail recursive.

The important concept to know in Haskell is guarded recursion (see tail recursion modulo cons), where any recursive calls occur within a data constructor (such as foldr, where the recursive call to foldr occurs as an argument to (:)). This allows the result of the function to be consumed lazily, since it can be evaluated up to the data constructor and the recursive call delayed until needed.

Tail call optimisation

In many programming languages, calling a function uses stack space, so a function that is tail recursive can build up a large stack of calls to itself, which wastes memory. Since in a tail call, the containing function is about to return, its environment can actually be discarded and the recursive call can be entered without creating a new stack frame. This trick is called tail call elimination or tail call optimisation and allows tail-recursive functions to recur indefinitely.

In Haskell, the function call model is a little different, function calls might not use a new stack frame, so making a function tail-recursive typically isn't as big a deal—being productive, via guarded recursion, is more usually a concern.

See also