Programming clojure-recursion and lazy-seq

Source: Internet
Author: User
Document directory
  • 5.1 functional programming concepts
  • 5.2 how to resolve Recursion
  • 5.4 recursion Revisited
5.1 functional programming concepts

The six rules
Although the benefits of FP are compelling, FP is a wholesale change from the imperative programming style that dominates much of the programming world today. plus, clojure takes a unique approach to FP that strikes a balance between academic purity and the reality of running well on the JVM. that means there is a lot to learn all at once.
But fear not. If you are new to FP, the following"Six rules of clojure FP"Will help you on your initial steps toward FP mastery, clojure style:
1.Avoid direct recursion, because the JVM will not be optimized, directly causing stack explosion
The JVM cannot optimize recursive CILS, and clojure programs that recurse will blow their stack.
2.Use recurWhen you are producingScalar values or small, fixed sequences
Clojure will optimize cballs that use an explicit recur.
3.When producing large or variable-sized sequences, always be lazy
(Do not recur.) then, your Callers can consume just the part of the sequence they actually need.

4.Be careful not to realize more of a lazy sequence than you need
5.Know the sequence Library
You can often write code without using recur or the lazy APIs at all.
6.Subdivide
Divide even simple-seeming problems into smaller pieces, and you will often find solutions in the sequence library that lead to more general, reusable code.

In fact, we only talk about two points,
Do not use recursion directly, use recur Optimization for small finite seq, use lazy for large or infinite seq, and do not realize the lazy seq part you do not need.

Do not build a car behind closed doors. Use the sequence library more.

 

5.2 how to resolve Recursion

FP uses recursion in large quantities,Of course, directly using recursion will cause stack explosion, so how to implement recursion becomes a problem. All FP needs to be solved...
The above 1, 2, 3, 4 rule clearly describes the method,
Otherwise, see pratical cljr-loop/recur.

The joy of clojure-laziness (6.3)

Lazy-seq, which must be in tail without recursion, but not all cases can be expressed as seq.

The two methods have the same idea. To avoid using Stack, it depends on the bottom-up design.

Functional Programs make great use of recursive definitions.

ARecursive DefinitionConsists of two parts:
Basis, Which explicitly enumerates some members of the sequence
Induction, Which provides rules for combining members of the sequence to produce additional members

Our challenge in this section is converting a recursive definition into working code.
There are always ways you might do this:
• A simple recursion, using a function that callitself in some way to implement the induction step.
• A tail recursion, using a function only calling itself at the tail end of its execution. tail recursion enables an important optimization.
• A lazy sequence that eliminates actual recursion and calculates a value later, when it is needed.

Choosing the right approach is important. implementing a recursive definition poorly can lead to code that performs terribly, consumes all available stack and fails, consumes all available heap and fails, or does all of these. in clojure, being lazy is often the right approach.

 

For example, Fibonacci numbers

Named for the Italian mathematician Leonardo (Fibonacci) of Pisa (c.1170-c.1250), the Fibonacci numbers were actually known to Indian mathematicians as far back as 200 BC. the maid properties, and they crop up again and again in algorithms, data structures, and even biology

The fibonaccis have a very simple recursive definition:
• Basis: F0, the zeroth Maid number, is zero. F1, the first Maid number, is one.
• Induction: For n> 1, FN equals FN −1 + FN −2.

(0 1 1 2 3 5 8 13 21 34)

Direct Recursion

The thought of top-down, F (n) = f (n-1) + f (n-2), fully need the support of stack

(defn stack-consuming-fibo [n]  (cond   (= n 0) 0      ;basis   (= n 1) 1      ;basis   :else (+ (stack-consuming-fibo (- n 1))        (stack-consuming-fibo (- n 2)))))   ;induction 

(Stack-consuming-fibo 9)
34

(Stack-consuming-fibo 1000000 );
Java. Lang. stackoverflowerror

Use recur, tail-call Optimization

Using the above logic directly means that tail-call optimization cannot be performed. You must change the logic to a bottom-up approach. If it is difficult to look at the recursion below, OK, I will write another method.

current = 0next = 1for(i =n; i>0; i--){
current = next next = current + next
}
The so-called tail optimization is actually, if you can write a for loop, then the compiler can optimize the stack call into a loop...
So the following recursive call is actually disguised as a recursive loop (because you cannot directly write a loop in FP, it must appear in the form of recursion)
(Defn recur-fibo [N] (letfn [(FIB; letfn is like let but is dedicated to lew.local functions [current next N]; FIB three parameters (if (zero? N) Current (Recur next (+ current next) (Dec n)]; tail-call optimization, replace function name fib (FIB 0 1 N) with recur ))) (recur-fibo 1000000) 195... 208,982 other digits... 875; does not cause stack explosion, because recur optimization, no stack is used

Why do we need to display the recur for tail-call optimization?

The problem here is the JVM. While Functional Extensions ages such as Haskell can perform TCO, the JVM was not designed for Functional Extensions ages.

No language that runs directly on the JVM can perform automatic TCO.

The absence of TCO is unfortunate but not a showstopper for functional programs. clojure provides several pragmatic workarounds:

Explicit Self-recursion with recur, lazy sequences, and explicit mutual recursion with trampoline.

 

Lazy, a better solution to recursive Problems

In the above issue about the number of Fibonacci, another way of thinking is to find the value of the Fibonacci of N, that is, to obtain the value of the nth item on the seq onacci seq.

So we can define lazy fib-seq. The idea here is different from the above. The logic is how to generate seq. More importantly, the idea of seq generation must be bottom-up.

This is crucial in FP, because only the bottom-up thinking is independent of stack, and the top-down thinking must be supported by stack.

For the reason why lazy-seq can solve the blow stack problem, refer to the joy of clojure-laziness (6.3)

(Defn lazy-seq-fibo ([] (Concat [0 1] (lazy-seq-fibo 0 1); Concat, two seq pairs ([a B] (let [n (+ a B)]; Calculate the next value (lazy-seq; lazy body (cons N (lazy-seq-fibo B n); cons, item and SEQ are connected in series.

 

(take 10 (lazy-seq-fibo))
(0 1 1 2 3 5 8 13 21 34)

(REM (Nth (lazy-seq-fibo) 1000000) 1000); REM calculates the remainder, and obtains the last three digits.
875

The above practices have already met 1, 2, 3, 4 Rule

ByRule 5, You can reuse existing sequence library functions that return lazy sequences.

Use the sequence library directly. Lazy seq is returned by default.

(defn fibo []
  (map first (iterate (fn [[a b]] [b (+ a b)]) [0 1])))

Lazy definitions consume some stack and heap.

But they do not consume resources proportional to the size of an entire (possibly infinite !) Sequence. Instead, you choose how many resources to consume when you traverse the sequence.

 

Coming to realization, monetization

Lazy sequences consume significant resources only as they are realized, that is, as a portion of the sequence is actually instantiated in memory.

Clojure works hard to be lazy and avoid realizing Sequences Until it is absolutely necessary.

(DEF lots-o-fibs (take 1000000000 (fibo); it defines a lot of fibo, but it is not executed.

(Nth lots-o-fibs 100); only the 100 of the computation will be performed, and the computation will not continue. This is the advantage of lazy.

 

Most sequence functions return lazy sequences.

If you are not sure whether a function returns a lazy sequence, the function's documentation string typically will tell you the answer:

(doc take)
-------------------------
clojure.core/take
([n coll])
Returns a lazy seq of the first n items in coll, or all items if there are fewer than n.

 

The repl, however, is not lazy.

The printer used by the repl will, by default, print the entirety of a collection.

That is why we stuffed the first billion fibonaccis into lots-o-fibs, instead of evaluating them at the REPL. don't enter the following at the repl:

; don't do this
(take 1000000000 (fibo))

Why is repl not lazy?

It doesn't mean that we should not lazy at this time, but should not lazy. lazy does not go to eval when it is not used, but runs (fibo) in repl. repl tries to print each value, which is equivalent to all eval values of force.
To solve this problem, you only need to limit the default number of repl prints.

(Set! * Print-length * 10)
(Fibo)
(0 1 1 2 3 5 8 13 21 34...); only eval10 times

 

Losing your head

; Holds the head (avoid !)
(DEF head-fibo (lazy-cat [0 1] (MAP + head-fibo (rest head-fibo ))))

(Nth head-fibo 1000000)
Java. Lang. outofmemoryerror: Java heap space; this is not a stack blew, It is a memory blew

The reason is that when lazy seq does not stop eval item, the previous value will be recycled, and the head-fiber is held here, and automatic recovery will fail, so that we can drop the memory blew...

Unless you want to cache a sequence as you traverse it, you must be careful not to keep a reference to the head of the sequence.

With lazy sequences, losing your head is often a good idea.

5.4 recursion Revisited

Http://www.blogjava.net/killme2008/archive/2010/07/14/326129.html, refer to this blog, said more clearly

Shortcutting recursion with memoization, notepad

Clojure. Core/memoize

([F])

Returns a memoized version of a referentially transparent function.

Memoized version of the function keeps a cache of the Mapping from arguments

To results and, when callwith the same arguments are repeated often, has

Higher performance at the expense of higher memory use.

It is very simple, in fact, to return the memoized version of the function, will be in the memory buffer before the results, when the call can be quickly returned.

The following example shows its purpose. If memoize is not available, blew stack will be applied, because this is a typical top-down approach, but memoize will not be used. Why?

Because the eval sequence of seq is small to large, and all results will be buffer, the subsequent recursion does not actually need to be done, because the result is already buffer...

(defn fac [n]          (if (<= n 1)              1              (* n (fac (dec n)))))(def fac (memoize fac))(def fac-seq (map fac (iterate inc 0)))(nth fac-seq 10000)
Mutual recursion and Trampoline

Http://www.blogjava.net/killme2008/archive/2010/08/22/329576.html

The typical issue of mutual recursion is parity. How can we solve the problem of blew stack?

(declare my-odd? my-even?)(defn my-odd? [n]      (if (= n 0)          false         (my-even? (dec n))))(defn my-even? [n]      (if (= n 0)          true         (my-odd? (dec n))))

 

Because two functions cannot be recur, you can use recur to encapsulate them into a function.

(defn parity [n]      (loop [n n par 0]            (if (= n 0)                par                (recur (dec n) (- 1 par)))))user=> (parity 3)1user=> (parity 100)0

But after all, this solution is not a good solution. clojure providesTrampolineTo solve this problem

Clojure's trampoline function invokes one of your mutually recursive functions:

(trampoline f & partial-args)
(defn trampoline  ([f]     (let [ret (f)]       (if (fn? ret)          (recur ret)          ret)))

Trampoline receives and calls a function as a parameter,

If the result is a function, use recur for tail recursive optimization,

If not, the returned result is mainly used to solve the issue of stack overflow of recursive calls to each other.

(Trampoline + 1 2); the function returns a result directly instead of a function.
3

To use trampoline, you only need to make a small change to change the original recursive call to an anonymous function.

(declare my-odd? my-even?)(defn my-odd? [n]      (if (= n 0)          false         #(my-even? (dec n))))(defn my-even? [n]      (if (= n 0)          true         #(my-odd? (dec n))))

User => (trampoline my-odd? 10000000); therefore, the blew stack will not be used in this way, because trampoline will use recur, like a jump bed. If it is a function, it will play repeatedly until the result is obtained.
False
User => (trampoline my-even? 10000)
True

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.