简体   繁体   中英

What is a good way, and a good language, to create a cap on recursive functions?

I have heard that recursion have the potential to overflow or memory leak due to every function call requiring some memory use. And I figured even if I wouldn't overflow the memory I might still like to cap the number of function calls. So using Javascript and throw I made this factorial function but I wonder if there is a more native way of doing it, or if there are programming languages that are more suited for this type of work?. Is there some solution that improves upon the linear memory use of such algorithms?

const CAPSIZE = 5
function factorial_req(a) {
  if( a == 1 ){
    factorial_req.stack_slack = CAPSIZE
    return 1
  }
  else if( factorial_req.stack_slack == 0 ){
    throw 'stack cap'
  }
  else {
    factorial_req.stack_slack--
    return a * factorial_req(a-1)
  }
}
factorial.stack_slack = CAPSIZE
function factorial(a) {
  try {
    return factorial_req(a)
  }
  catch(err) {
    if( err == 'stack cap' )
      return 0 // 0 == flase
    else
      throw err
  }
}
// test
console.log( `5! = ${factorial(5)} , !3 = ${factorial(3)} and !9 == 0 is ${factorial(9) == 0} and !9 == false is ${factorial(9) == false} while !99999999 == true is ${factorial(99999999) == true}`)

The algorithm you wrote uses linear space. You can't implement your algorithm in less than linear space. The key is to use a tail-recursive algorithm. The correct implementation of tail recursion does not result in linear space usage.

Consider a basic implementation of the factorial function, written in pseudocode

factorial(n) = 1 if n == 0
               n * factorial(n - 1) otherwise

which is basically the definition of the factorial function. Then the evaluation of factorial(3) proceeds as follows:

factorial(3)
3 * factorial(2)
3 * (2 * factorial(1))
3 * (2 * (1 * (factorial(0)))
3 * (2 * (1 * 1))
3 * (2 * 1)
3 * 2
6

Note that it is the very nature of the algorithm which requires linear space.

Now consider a different implementation of factorial. We want to write a helper function called factorial_helper which satisfies the property factorial_helper(acc, n) = acc * n! . This gives us the following algorithm:

factorial(n) = factorial_helper(1, n)

factorial_helper(acc, n) = acc if n == 0
                           factorial_helper(acc * n, n - 1) otherwise

Now consider the evaluation of factorial(3) .

factorial(3)
factorial_helper(1, 3)
factorial_helper(3, 2)
factorial_helper(6, 1)
factorial_helper(6, 0)
6

Notice that the evaluation of factorial(n) uses constant space (assuming an integer takes up constant space) under the second implementation. This is because factorial_helper is tail-recursive. This means that whenever, in the computation of factorial_helper(a, b) , we make a recursive call, the result of that recursive call is exactly factorial_helper(a, b) (there's no extra processing). In other words, the recursive call in factorial_helper is, if it occurs, the "last thing factorial_helper does".

Unfortunately, JavaScript is currently not guaranteed to properly implement tail recursion. This makes it a rather unsuitable language for recursion generally.

It is always possible to implement recursive functions iteratively by switching to continuation passing style. Languages like Haskell make this very easy. For example, implementing factorial in Haskell looks like this:

factorial n = if n == 0 
              then 1
              else n * factorial (n - 1)

And in continuation passing style, this looks like

factorial n = if n == 0
              then return 1
              else (n *) <$> factorial (n - 1)

It's a very minor syntactic change, but factorial is now implemented using continuations and will thus not suffer from a stack overflow (although it still uses linear memory in n ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM