37

I've just learnt how lazy evaluation works and I was wondering: why isn't lazy evaluation applied in every software currently produced? Why still using eager evaluation?

John Smith
  • 1,739

6 Answers6

42

Lazy evaluation requires book-keeping overhead- you have to know if it's been evaluated yet and such things. Eager evaluation is always evaluated, so you don't have to know. This is especially true in concurrent contexts.

Secondly, it's trivial to convert eager evaluation into lazy evaluation by packaging it into a function object to be called later, if you so wish.

Thirdly, lazy evaluation implies a loss of control. What if I lazily evaluated reading a file from a disk? Or getting the time? That's not acceptable.

Eager evaluation can be more efficient and more controllable, and is trivially converted to lazy evaluation. Why would you want lazy evaluation?

DeadMG
  • 36,914
19

Mainly because lazy code and state can mix badly and cause some hard to find bugs. If the state of a dependent object changes the value of your lazy object can be wrong when evaluated. It's much better to have the programmer explicitly code the object to be lazy when he/she knows the situation is appropriate.

On a side note Haskell uses Lazy evaluation for everything. This is possible because it's a functional language and doesn't use state (except in a few exceptional circumstances where they are clearly marked)

Tom Squires
  • 17,835
15

Lazy evaluation's is not always better.

The performance benefits of lazy evaluation can be great, but it is not hard to avoid most unnecessary evaluation in eager environments- surely lazy makes it easy and complete, but rarely is unnecessary evaluation in code a major problem.

The good thing about lazy evaluation is when it lets you write clearer code; getting the 10th prime by filtering an infinite natural numbers list and taking the 10th element of that list is one of the most concise and clear way of proceeding: (pseudocode)

let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)

I believe it would be quite difficult to express things so concisely without lazyness.

But lazyness isn't the answer to everything. For starters, lazyness cannot be applied transparently in the presence of state, and I believe statefulness cannot be automatically detected (unless you are working in say, Haskell, when state is quite explicit). So, in most languages, lazyness needs to be done manually, which makes things less clear and thus removes one of the big benefits of lazy eval.

Furthermore, lazyness has performance drawbacks, as it incurs a significant overhead of keeping non-evaluated expressions around; they use up storage and they are slower to work with than simple values. It is not uncommon to find out that you have to eager-ify code because the lazy version is dog slow- and it is sometimes hard to reason about performance.

As it tends to happen, there is no absolute best strategy. Lazy is great if you can write better code taking advantage of infinite data structures or other strategies it allows you to use, but eager can be easier to optimize.

alex
  • 2,914
6

Here is a short comparison of the pros and cons of eager and lazy evaluation:

  • Eager evaluation:

    • Potential overhead of needlessly evaluating stuff.

    • Unhindered, fast evaluation.

  • Lazy evaluation:

    • No unnecessary evaluation.

    • Bookkeeping overhead at every use of a value.

So, if you have many expressions that never have to be evaluated, lazy is better; yet if you never have an expression that does not need to be evaluated, lazy is pure overhead.

Now, lets take a look at real world software: How many of the functions that you write do not require evaluation of all their arguments? Especially with the modern short functions that only do one thing, the percentage of functions fall into this category is very low. Thus, lazy evaluation would just introduce the bookkeeping overhead most of the time, without the chance to actually save anything.

Consequently, lazy evaluation simply does not pay on average, eager evaluation is the better fit for modern code.

3

As @DeadMG noted Lazy evaluation requires book-keeping overhead. This can be expensive relative to eager evaluation. Consider this statement:

i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3

This will take a bit of calculation to calculate. If I use lazy evaluation, then I need to check if it has been evaluated every time I use it. If this is inside a heavily used tight loop then the overhead increases significantly, but there is no benefit.

With eager evaluation and a decent compiler the formula is calculated at compile time. Most optimizers will move the assignment out of any loops it occurs in if appropriate.

Lazy evaluation is best suited to loading data which will be infrequently accessed and has a high overhead to retrieve. It is therefore more appropriate to edge cases than core functionality.

In general it is good practice to evaluate things that are frequently accessed as early as possible. Lazy evaluation does not work with this practice. If you will always access something, all lazy evaluation will do is add overhead. The cost/benefit of using lazy evaluation decreases as the item being accessed becomes less likely to be accessed.

Always using lazy evaluation also implies early optimization. This is a bad practice which often results in code which is much more complex and expensive that might otherwise be the case. Unfortunately, premature optimization often results in code that performs slower than simpler code. Until you can measure the effect of optimization, it is a bad idea to optimize your code.

Avoiding premature optimization does not conflict with good coding practices. If good practices were not applied, initial optimizations may consist of applying good coding practices such as moving calculations out of loops.

BillThor
  • 6,310
3

If we potentially have to fully evaluate an expression to determine it's value then lazy evaluation can be a disadvantage. Say we have a long list of boolean values and we want to find out if all of them are true:

[True, True, True, ... False]

In order to do this we have to look at every element in the list, no matter what, so there is no possibility of lazily cutting off evaluation. We can use a fold to determine if all the boolean values in the list are true. If we use a fold right, which uses lazy evaluation, we don't get any of the benefits of lazy evaluation because we have to look at every element in the list:

foldr (&&) True [True, True, True, ... False] 
> 0.27 secs

A fold right will be much slower in this case than a strict fold left, which does not use lazy evaluation:

foldl' (&&) True [True, True, True, ... False] 
> 0.09 secs

The reason is a strict fold left uses tail recursion, which means it accumulates the return value and doesn't build up and store in memory a large chain of operations. This is much faster than the lazy fold right because both functions have to look at the entire list anyway and the fold right can't use tail recursion. So, the point is, you should use whatever is best for the task at hand.