I am experimenting with clojure's lazy sequences. In order to see when the evaluation of an item would occur, I created a function called square that prints the result before returning it. I then apply this function to a vector using map.
(defn square [x]
(let [result (* x x)]
(println "printing " result)
result))
(def s (map square [1 2 3 4 5])) ; outputs nothing
Here in my declaration of s, the REPL does not output anything. This signals that the computation has not started yet. This appears to be correct. I then do:
(first s)
The function "first" takes only the first item. So I expect that only 1 will be evaluated. My expectation is the REPL will output the following:
printing 1
1
However, the REPL outputted the following instead.
printing 1
printing 4
printing 9
printing 16
printing 25
1
So rather than evaluating only the first item, it seems it evaluates all items, even though I am accessing just the first item.
If the state of a lazy sequence can only be either all values computed and no values computed, then how can it gain the advantages of lazy evaluation? I came from a scheme background and I was expecting more like the behavior of streams. Looks like I am mistaken. Can anyone explain what is going on?
Lazy evaluation is used in Unix map functions to improve their performance by loading only required pages from the disk. No memory will be allocated for the remaining pages.
Lazy evaluation is a form of non-strict evaluation in which arguments are not evaluated until required. A computation that does not terminate under strict evaluation, can terminate under lazy evaluation.
In programming language theory, lazy evaluation, or call-by-need, is an evaluation strategy which delays the evaluation of an expression until its value is needed (non-strict evaluation) and which also avoids repeated evaluations (sharing).
Lazy sequences are regular sequences where each item is computed on demand rather than up front. For example, consider this array of numbers: let numbers = Array(1... 100000) That will hold 100,000 numbers.
What is Lazy Evaluation? — Programming Word of the Day When a compiler encounters an expression, usually it tries to evaluate it. Evaluation is the process of getting the root meaning of a piece of code. The meaning of 5 + 3 is 8.
The style of only evaluating what's needed is called lazy evaluation, while the opposite (evaluating immediately) is called strict evaluation. It’s called “lazy” because the compiler will procrastinate. It will not be a good student and study in advance.
This means if we call a function sequence defined as which creates an array of integers between l and u and then maps each number to its square our language will fully evaluate sequence (1, 5) before then mapping each value in that array. We are then left with an array squares that has been fully evaluated before we move on the next line of code.
Just like lazy students, a lazy compiler can be much more efficient if it makes smart choices. For lazy evaluation to be efficient, it needs to use memoization. In other words, it needs to keep a dictionary where the key is the name of the variable, and the value is the result of the evaluation.
Laziness isn't all-or-nothing, but some implementations of seq operate on 'chunks' of the input sequence (see here for an explanation). This is the case for vector which you can test for with chunked-seq?
:
(chunked-seq? (seq [1 2 3 4 5]))
When given a collection map
checks to see if the underlying sequence is chunked, and if so evaluates the result on a per-chunk basis instead of an element at a time.
The chunk size is usually 32 so you can see this behaviour by comparing the result of
(first (map square (vec (range 35))))
This should only display a message for the first 32 items and not the entire sequence.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With