Pratical cljr-loop/recur

Source: Internet
Author: User

Programming clojure is too simple to write, pratical clojure is well written, this book is well written in some chapters, discuss why, rather than just how, so excerpt

First, clojure does not provide direct looping syntax !!! This understanding is important.

Because looping is the concept of imperative, for FP, it is necessary to change the way of thinking and use recursion to solve the same problem. So the following also describes the programmers who have transferred from imperative, it is a great challenge to think about problems from the perspective of recursion.

It will probably come as a minorShockTo users of imperative programming versions that clojure provides no direct looping syntax.
Instead, like other functional ages, it uses recursion in scenarios where it is necessary to execute the same code multiple times.

Thinking recursivelyIs one ofLargest challengesComingFrom imperative to functional ages, But it is surprisingly powerful and elegant, and you will soon learn how to easily express any repeated computation using recursion.

For recursion, parameters must be used to continuously pass the intermediate computing results, instead of some global variables, and a base case must be used as the end condition, each iteration needs to modify base condition and check.

For each tive recursion in clojure (or any other functional language, for that matter), you only need to keep these guidelines in mind:

• Use a recursive function's arguments to store and modify the progress of a computation. In imperative programming languages, loops usually work by repeatedly modifying a single variable.

• Make sure the recursion has a base case or base condition.

• With every iteration, the recursion must make at least some progress towards the base condition.

 

The following are two examples of recursion: root number and power multiplication.

Uses Newton's algorithm to recursively calculate the square root of any number.
Very interesting. I always wanted to use the root number. I never thought about how it was implemented. I tried and got an error, approached, and returned when the error was less than 0.001.

(Defn abs "calculates the absolute value of A number" [N] (if (<n 0) (*-1 N) n )) (defn AVG "returns the average of two arguments" [a B] (/(+ A B) 2) (defn good-enough? "Tests if a guess is close enough to the real square root" [number guess] (let [diff (-(* guess) Number)] (if (<(ABS diff) 0.001) True False) (defn SQRT "returns the square root of the supplied number" ([number] (SQRT number 1.0 )); clojure Implementation of default parameters is really troublesome ([number guess] (if (good-enough? Number guess) Guess (SQRT number (avg guess (/number guess ))))))

Uses Recursion to calculate exponents, Power Operation

(defn power  "Calculates a number to the power of a provided exponent."  [number exponent]  (if (zero? exponent)    1    (* number (power number (- exponent 1)))))

 

There are actually two types of recursion: top-down, bottom-up

In imperative language, this recursion can be simply replaced by a for loop. It sets the initial value and continues iteration until the conditions are met. this idea is actually expressed by recursion, but not very clear. It is easier to understand it by using loops, but it can only be implemented by recursion for FP.

From top down, xn = function (Xn-1), such as power is xn = x * (Xn-1), a typical recursive approach, suchIn fact, it is more clear to use the top-down thinking and code, and more suitable to use FP.

 

Recursion is the ideal way of thinking and programming for FP, but there is always a gap between ideal and reality...

I personally think that because the current FP is running on the Turing machine, but in fact the Turing machine is designed for imperative, it cannot fully exert the power of FP.

There is a problem with recursion that it consumes too much stack space, especially when the number of layers of recursion is large, it will become a large issue. Therefore, FP needs to solve this problem when implementing recursion.

The top-down thinking must depend on Stack and cannot be avoided.

In fact, we can only use the bottom-up idea for design, because this idea is actually a loop, so the compiler can easily be optimized, so that the stack is not used...

This is actually a helpless compromise,

I think the idea of using top-down is the idea of pure FP. In fact, you can't write it like this, but you still need to write it in a circular way.

The bottom-up optimization method is tail-call optimization. When recursion occurs at tail position, the compiler will optimize it to an iteration that does not consume stacks, that is, a for loop.

In fact, I personally think this rule is a bit difficult to understand. I don't understand it like this. As mentioned above, there are two types of recursion,

In fact, they also comply with this rule,

For SQRT,Bottom-up thinkingIt is a loop. Recursive points are generally at the tail position.

And for the idea of the top-down, are based on this formula, xn = function (Xn-1), such as power is xn = x * (Xn-1), so recursion is impossible in the tail position

 

One practical problem with recursion is that, due to the hardware limitations of physical computers, there is a limit on the number of nested functions (the size of the stack ).
There is a strict limit on the number of times a function can recur. for small functions, this rarely matters. but if recursion is a generic and complete replacement for loops, it becomes an issue. there are missing situations in which it is necessary to iterate or recur indefinitely.

Historically, functional extensions ages resolve this issue through tail-call optimization. tail-call optimization means that, if certain conditions are met, the compiler can optimize the recursive callin such a way that they do not consume stack. under the covers, they're implemented as iterations in the compiled machine code.

The only requirement for a recursive call to be optimized in most functional versions is that the call occurs in tail position. There are several formal definitionsTail position, But the easiest to remember, and the most important, is that it isThe last thing a function does before returning.

Clojure's recur

For most FP systems, tail call optimization is automatically implemented by the compiler. programmers do not have to worry about this issue. Therefore, for most FP systems, there is no looping syntax, and all are implemented through recursion.

However, clojure does not do this, which is also under attack by many people. clojure needs to explicitly mark tail call Optimization Using recur form (simply replacing the function name with recur.

In a sense, recur has no value or need at all. The compiler should automatically complete tail call optimization, but clojure does not, mainly due to JVM restrictions, it is difficult to achieve automatic optimization.

In addition, this tail call optimization can only occur in the tail position. If the programmer uses an error, the compiler will complain ......

In some functional versions, such as scheme, tail call optimization happens automatically whenever a recursive call is in tail position.

Clojure does not do this. In order to have tail recursion in clojure, it is necessary to indicate it explicitly usingRecur form.

To useRecur, Just call it instead ofFunction NameWhenever you want to make a recursive call. It will automatically call the containing function with tail-call optimization enabled.

 

Clojure has come under fire from some quarters for not doing tail-call optimization by default, whenever possible,Without the need for the recur special form.

Although the partition tion of recur was spurred byLimitations of the JVMThat make it difficult to do automatic tail optimization, please members of the clojure community find that having explicit tail recursion is much clearer and more convenient than having it implicitly assumed. with clojure, you can tell at a glance if a function is tail recursive or not, and it's impossible to make a mistake.

If something uses recur, it's guaranteed never to run out of stack space due to recursion. and if you try to use recur somewhere other than in correct tail position, the compiler will complain. you are never left wondering whether a call is actually in tail position or not.

Example: Add-up,

(defn add-up  "adds all the numbers below a given limit"  ([limit] (add-up limit 0 0 ))  ([limit current sum]  (if (< limit current)    sum    (add-up limit (+ 1 current) (+ current sum)))))

In the initial version, the pure FP approach is used to solve the problem with recursion... The problem arises. When the number of recursion times is large (in the following example, 5000 times), stack overflow will occur.

user=> (add-up 3)6user=> (add-up 500)125250user=> (add-up 5000)java.lang.StackOverflowError

Clojure does not optimize itself. You must use recur to replace the function name. The problem is solved because it does not actually recursion, but iteration.

(defn add-up  "adds all the numbers up to a limit"  ([limit] (add-up limit 0 0 ))  ([limit current sum]  (if (< limit current)    sum    (recur limit (+ 1 current) (+ current sum)))))user=> (add-up 5000)12502500

 

Using Loop

As mentioned above, recur is an unnecessary design or a forced compromise. This is especially true for loop.

Since we can only use the bottom-up idea to write FP programs, but in fact this idea is implemented using recursion, I think it is quite awkward. Maybe others think that, in fact, the compiler will eventually optimize it into a loop.

Therefore, it is easier to create a loop, because it cannot use the idea of pure FP... Recur makes it easier to use.

For the benefits of loop, although a lot of data is written, there are two points

It is more concise and does not need to define functions. More importantly, it simplifies the initialization of the previous initial conditions.

The second is what we mentioned above. Since it is actually a loop, we can write it in a loop.

To define a loop construct, use the loop form. It in turn takes two forms: first, a vector of initial argument bindings (in name/value pairs) and an expression for the body.

(defn add-up  "adds all the numbers up to a limit"  ([limit]   (loop [current 0 sum 0]    (if (< limit current)      sum      (recur limit (+ 1 current) (+ current sum)))))user=> (add-up 5000)12502500

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.