The joy of clojure-laziness (6.3)

Source: Internet
Author: User
Tags gc overhead limit exceeded
Document directory
  • Understanding the lazy-seq recipe
  • The delay and force macros

Clojure isPartiallyA lazy language.

Clojure is lazy in the way itHandlesItsSequenceTypes.

Laziness is an important feature of FP. Pure FP should be lazy language, such as Haskell. Of course, the complete lazy problem is that the execution sequence of programs cannot be guaranteed.
Clojure compromises between practicality and pure FP, so it only partially supports lazy, mainly in processing sequence, in most other cases, it is still the eager (not lazy)

For example, typical examples, function parameters,
(-13 (+ 2 2 ))
For eager, calculate 2 + 2 = 4 first, and then pass 4
The lazy method is to pass (+ 2 2) directly to the subtraction method and calculate 4 only when calculation is required.

 

Since clojure is lazy only when processing sequence, let's take a look at clojure's lazy-seq.

Understanding the lazy-seq recipe

Let's take an example to see why lazy-seq is required?

(Defn rec-step [[x & Xs]; the first item is passed in X, and the rest is passed in Xs.
(If x
[X (REC-step XS)]
[])


(REC-step [1 2 3 4])
; => [1 [2 [3 [4 []

(REC-step (range 1, 200000 ))
; => JAVA. Lang. stackoverflowerror

Will Blow stack, because the use of recursive functions, so to solve this problem, do not use tail optimization, but here the use of lazy-seq is more appropriate

The following recipe...

Lazy-seq recipe for applying laziness to your own functions:

1. UseLazy-seq macroAt the outermost level of your lazy sequence producing expression (s ).

2. If you happen to be consuming another sequence during your operations, thenUse 'Rest' instead of 'Next'Because next is realize more than rest once, because next must first judge whether it is null

3. Prefer higher-order functions when processing sequences.

4. Don't hold onto your head.

According to this recipe, lazy version is implemented as follows, but it is simply added with lazy-seq encapsulation (first, rest is actually equivalent to the above statement)

(defn lz-rec-step [s]
  (lazy-seq
    (if (seq s)
    [(first s) (lz-rec-step (rest s))]
    [])))

The simpler form is as follows,

(defn simple-range [i limit]
  (lazy-seq
    (when (< i limit)
    (cons i (simple-range (inc i) limit)))))

(simple-range 0 9)
;=> (0 1 2 3 4 5 6 7 8)


(Simple-range 0 200000) Will blew stack occur?

No, because recursion is not actually used.

You can imagine that lazy is meaningless if recursion is used directly, and Recursion needs to get the value from bottom-to-bottom backtracking.

So although lazy-seq is added here, it still seems that the function is called recursively. In fact, lazy-seq is optimized here.

 

So what is the optimization of lazy-seq?

Each step of a lazy seq may be in one of two States.
Unrealized state, It'll contain a function or closure of no arguments (a thunk) that can be called later to realize the step.

Realized state, The thunk's return value is cached instead, and the thunk itself is released

Note that although not shown here,Realized lazy seqMay simply contain nothing at all, called nil, indicating the end Of the seq.

Clearly shows how lazy-seq is implemented.

At the beginning, all items were unrealized. In fact, my understanding is a closure (simple-range 0 9)

Each time you call first, realize will be performed and a realized item will be generated. Two first calls will generate two realized items, 0, 1, while rest gets the remaining unrealized items (simple-range 2 9)

We can see that the stack is not used at all, so there will be no blow stack.

It should be noted that the realized item is only cached in the memory. If it is not referenced, it will be automatically reclaimed.Realized lazy seq does not contain any results. Nil is used to point to the end of seq.

Therefore, for a large seq, do not hold the head, that is, do not refer realized item, which will lead to the failure to be automatically recycled, resulting in outofmemory

 

For the lose head, you can refer to the following example, but the order of (first R) (Last R) is different, it will cause outofmemory

(let [r (range 1e9)] [(first r) (last r)])
;=> [0 999999999]
(let [r (range 1e9)] [(last r) (first r)])
; java.lang.OutOfMemoryError: GC overhead limit exceeded

The reason is the second example. The main first item must be held all the time, so JVM cannot perform GC, so outofmemoryerror

Because clojure is not a pure FP, it must be executed in strict order. For pure FP such as Haskell, this problem can be solved through simple complier optimization.

If this example is not clear enough, read this blog and draw a picture. It is clear that the header persistence problem of clojure's inert Sequence

 

The delay and force macros

Although clojure sequences are largely lazy, clojure itself isn' t.

In most cases, expressions in clojure are evaluated once prior to their being passed into a function rather than at the time of need. But clojure does provide mechanic ISMs for implementing what are knownCall-by-need Semantics. The most obvious of these mechanisms is itsmacro facilities, but we'll defer that discussion until Chapter 8.

The other mechanic for providing what we'll callExplicit lazinessAre clojure'sDelayAndForce.

In short, the delay macro is used to defer the evaluation of an expression until explicitly forced using the force function.

Clojure is not a lazy language, but it also supports the call by need semantics. The simple method is to use delay, force

Example,

(Defn INF-Triangles [N]; The function returns the lazy linked-list, head storage value, and tail storage to generate the closure of the next item.
{: Head (Triangle N); triangle indicates the computing Logic
: Tail (delay (INF-triangles (INC n)}); Using delay, the function is not actually called.

 

(Defn head [LIST] (: Head list ))
(Defn tail [LIST] (Force (: tail list); use force to force the closure of delay to execute

 

Define tri-nums. This lazy structure is called 'head strict '. Unlike lazy-seq, because the head value is first realize, while lazy-seq does not realize, it must be shown that the first

(def tri-nums (inf-triangles 1))
(head tri-nums)
;=> 1
(head (tail tri-nums))
;=> 3
(head (tail (tail tri-nums)))
;=> 6

 

Of course, writing programs using delay and force is an onerous way to go about the problem of laziness, and you 'd beBetter served by using clojure's lazy SequencesTo full effect rather than building your own from these basic blocks.

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.