Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

[REBOL] Re: Tail end recursion

From: joel:neely:fedex at: 9-Oct-2003 10:22

Hi, Maxim, and all Maxim Olivier-Adlhoch wrote:
> so basically its like using recursion to do a loop? >
...
> but how does tail-end recursion stop? >
Recursion and looping are just two ways of expressing that something is to be repeated. In both cases one needs a terminating condition (unless the process is to run forever...) The example given earlier was a tad to trivial to make that point, but consider instead the slightly more complete example: forr-count: func [limit [integer!]] [ for i 0 limit 1 [print i] ] As we all know, this is just syntactic sugar for: while-count: func [limit [integer!] /local i] [ i: 0 while [i <= limit] [ print i i: i + 1 ] ] Notice that this version clearly states the initial "state" (I = 1), the condition for continuing the computation (I <= LIMIT), and the evaluation to occur for each continued case (PRINT and increment). Those same three ingredients are required to write an equivalent recursive function: rec-count: func [limit [integer!] /local .rec-count] [ do .rec-count: func [ i [integer!] .limit [integer!] ][ if i <= .limit [ print i .rec-count i + 1 .limit ] ] 0 limit ] Notice that every evaluation path through the inner function (.REC-COUNT) either terminates the computation or ends with a recursive call on itself, and every call to the inner function is the last thing in an execution path. (There's one of each.) In such cases, some languages/implementations can avoid the state-saving/restoration normally associated with beginning a subordinate evaluation -- in other words, they transform the recursive inner function above into a loop. REBOL does not. Sometimes the clearest/simplest specification for how to solve some problem is based on a divide-and-conquer strategy, where the sub-problems are either trivial to solve or are smaller cases similar to the original problem. In these cases, it is often easier to write a recursive solution. If the state management of the recursive implementation is too costly, one can (attempt to! ;-) formally transform the recursive solution into an iterative one; sometimes that's easy and sometimes not. (*) For more on this subject, please see http://www.rebolforces.com/articles/ria/ and for more on why recursion isn't complicated, see http://www.rebolforces.com/articles/metaphors/ HTH! -jn- (*) For an example of a problem that's easy to solve recursively, but which takes a bit of thought to solve iteratively (or in closed form! ;-), consider the old Fibonacci numbers. The original problem can be framed as follows: Begin a pair of new-born rabbits (one male, one female). When a pair of rabbits reaches age one month, they begin breeding, with a one-month gestation period, and breeding again immediatly after birth. Each breeding produces a pair of rabbits (one mail, one female). How many pairs of rabbits does one have each month (assuming that rabbits are immortal, food is infinitely available, etc...) Classify them as mature (at least one month old, breeding every month) and immature (just born, not mature for one more month). This give us the following population chart (in pairs): Month Mature Immature Total ----- ------ -------- ----- 1 0 1 1 2 1 0 1 3 1 1 2 4 2 1 3 5 3 2 5 etc... In other words, all pairs (immature or mature) which were alive last month are still alive, and all mature pairs from last month have produced new pairs. But the mature pairs last month are just the total population the month before! Finally, during the first two months we only have the original pair (immature or mature), so we get rfib1: func [n [integer!]] [ either n < 3 [1] [(rfib1 n - 1) + (rfib1 n - 2)] ] A little "programming algebra" produces the equivalent iterative form ifib1: func [n [integer!] /local a b] [ a: 0 b: 1 loop n [ a: (b: b + a) - a ] a ] But it takes a bit more mathematics to produce the equivalent closed form solution cfib1: func [n [integer!] /local r] [ r: square-root 5 ((1 + r / 2) ** n) - ((1 - r / 2) ** n) / r ] So recursion and iteration both have their places! ;-) -- ---------------------------------------------------------------------- Joel Neely joelDOTneelyATfedexDOTcom 901-263-4446 Counting lines of code is to software development as counting bricks is to urban development.