Reasons for recursive functions

In this video , during the 28-minute mark, Brian Harvey asked the student if we should always use the iterative process for the recursive process when writing programs. He said no because

Your programs are not going to limit space. And in terms of the location of what is in memory, you should have a lot more control than you, compared to how the program is interpreted in order to really influence it.

Since this is not a circuit, I suggested that he generally speaks of programming languages. And when he said: "Your programs will not be launched into space." Does it really ignore stack overflows? I am confused by his answer, because I don’t have it, so you have already run out of space with function calls? I don’t understand anything from the part “in terms of locality”. Stack overflows can occur using schema, java, and other languages. Am I right, or do I not understand his statement?

+5
source share
3 answers

The video you are linking to is a lecture in computer science. Computer science is largely theoretical and considers many of the details of computing that are not relevant to practicality. In this case, he says, by the beginning of the lecture, today computers are large and fast enough that performance is rarely a problem.


Memory locality is not related to StackOverflowException s, in any language. In fact, the locality of memory refers to SRAM (static RAM), which contains a cache of adjacent data brought in each bus load from memory (it can be either a disk or RAM). Retrieving data from this cache is faster than retrieving it from memory, so the program will run faster if all the data needed for several consecutive operations is in the cache.


Now that everyone is very low level. Behind most (if not all) modern languages, such as Java, there is a compiler that works on many low-level optimizations. This means, firstly, that little can be done to optimize your code at a low level, especially without interfering with compiler optimization. Secondly, (as he says right after the segment you are talking about), if you are not making a resource-intensive game, you should not worry about performance (unless you have noticeable performance problems, but this is more an indication of other problems in the code) .

+1
source

Nowadays, with our huge memory stack overflows, it is often a sign of infinite recursion, just as an iterative program without stopping is a sign of an infinite loop.

So he is right.

+1
source

Will my recursive procedure cause stack overflows? It depends on the type of recursive procedure that you have developed, depending on the problem, most naive recursions can be converted to tail calls (in tail-optimized TCO languages), which allows recursion to work in a constant memory space without using mutations or other conditions related to the state.

In the diagram, an iterative process:

 (let ((i 0) (max 10)) (let loop () (cond ((< i max) (printf "~A~N" i) (set! i (+ i 1)) (loop)) (else i)))) 

This procedure uses read-only memory, which is equal to the space required to store the call in a loop on the stack. this procedure is not a function, it uses a mutation for iteration (its also recursion;), but ..).

There are two recursions in the schema:

 (define (fact-1 n) (cond ((eq? n 1) n) (else (* n (fact-1 (- n 1)))))) (define (fact-2 n carry) (cond ((eq? n 1) carry) (else (fact-2 (- n 1) (* carry n))))) 

Fact-1 is normal recursion and very functional, there is no change in state, instead, memory usage increases when new lexical closures are created with every fact-1 call, which ultimately ends the stack. He grows like

 =>(fact-1 10) ..(* 10 (fact-1 9)) ..(* 10 (* 9 (fact-1 8))) ..(* 10 (* 9 (* 8 (fact-1 7)))) .. ..... ..(* 10 (... (* 2 1) ...)) .. ..... ..(* 10 362880) =>3628800 

While Fact-2 is recursive, but in a tail form, so instead of creating a stack and folding calls in the basic case, the value is passed forward, and we get this:

 =>(fact-2 10 1) ..(fact-2 9 10) ..(fact-2 8 90) ..(fact-2 7 720) .. .......(fact-2 1 362880) =>3628800 

This is equivalent to turning fact-1 into a process of interaction, but without mutation, because the values ​​are passed forward instead of being assigned. Note that each call still creates a new lexical closure, but since the function does not return to the original caller, but to the original location of the caller’s stack, the compiler can discard previous locks instead of having them nested inside each other, re-bind the variables on each recursion level.

So where should I use recursion versus iteration It completely depends on the process that needs to be developed, and on the language used. If your language does not support TCO, you will need to use small recursions and write down cyclic (recursive or iterative) stateful procedures. If you have a TCO, then it is best to use recursion, or tail calls, or stateful state, or a combination of these. Not all recursive procedures can be written as tail, and not all iterative processes can be written as recursion. If you are concerned about memory usage and want deep recursions, you should use Tail-calls.

Note: Some of you may have noticed, but the first procedure is actually a tail, but the example still illustrates the point of normal iteration executing the state! things and work in constant maximum memory, regardless of all valid input data.

+1
source

Source: https://habr.com/ru/post/1238410/


All Articles