Imposing the Edges Later 12
Here’s what wikipedia has to say about lazy evaluation:
The benefits of lazy evaluation include: performance increases due to avoiding unnecessary calculations, avoiding error conditions in the evaluation of compound expressions, the ability to construct infinite data structures, and the ability to define control structures as regular functions rather than built-in primitives.
It’s all true, but I think it misses a point. One of the nicest things about lazy evaluation is that it enables a different form of abstraction. Take a look at this code. It computes the Mandelbrot Set. There are many ways to code up this algorithm, but the code I just linked to is very typical – it’s a doubly nested loop which iterates over a specific rectangle in the complex plane. It’s hard to see anything that’s awkward about its iteration strategy in a procedural language – the algorithm does require starting and ending points in the plane.
Let’s look at a different way of approaching this. Here’s part of a Mandelbrot Set generator in Haskell:
mandelbrot :: Double -> [[Int]]
mandelbrot incrementSize =
[[ escapeIterations $ translate offset . translate (x,y) . mandel
| x <- increments]
| y <- increments]
where increments = [0.0, incrementSize .. ]
window :: (Int, Int) -> (Int, Int) -> [[a]] -> [[a]]
window (x0, x1) (y0, y1) = range y0 y1 . map (range x0 x1)
where range m n = take (n-m) . drop m
The interesting bit is the mandelbrot function. It contains a doubly-nested list comprehension which, essentially, mimics the behavior of a doubly-nested loop in the procedural algorithm. However, there is one interesting difference. This list comprehension actually represents the mandelbrot computation across a full quarter of the infinite plane – it goes from zero to infinity in x and y. All we have to do is pass it an increment size and it is configured to create that grid. If we want to zoom in to a particular place in the Mandelbrot Set, we can use the window function like this:
-- compute the mandelbrot from (10,10) inclusive to
-- (20,20) exclusive with an increment of 0.05
window (10, 20) (10, 20) $ mandelbrot 0.05
The trick to this code is in window’s range computation. It takes a starting position, an ending position, and a list and it drops all of the elements of the list before the starting position, without evaluating them, and then it takes, from the front of the new list, end - start elements, giving us a sub-range from the original list. When that elements of that sub-list are evaluated, the computation for each point in the window occurs.
This code yields the computation that we want and the neat thing is, we are really only computing the piece we specified with window. We’ve essentially formed a general computation and imposed the edges later.
Looking back now at the imperative mandelbrot code, it’s easy to see that it was, in a way, mixing two concerns. The code which determined the range was mixed with the code which generated the set. Laziness allows us to separate the two concerns.
Note: There are much more direct ways of computing the Mandelbrot Set in Haskell. Here’s one which is particularly brief.

As a Haskell fan I’m searching for examples where the lazy-by-default approach of Haskell beats other like Python’s iterators… the only one I’m aware of is when you use the “tying-the-knot” techniques which has more to do with performance than with good software engineering practices…
Cristiano: Are you talking about this? http://www.haskell.org/haskellwiki/Tying_the_Knot
Michael,
Interesting read, indeed lazy eval is extremely useful for all forms of culling.
I am personally very interested in picking up a purely functional lazy language to do some experimenting in.
For my personal enjoyment I am fanatical about seeing my results so I did a lot of hobby coding with C++ and OpenGL.
Which functional language will be easiest to get me drawing nice graphical things?
Regards Dawid
Sorry wrong email
Dawid: I think Clean and Miranda are fully lazy also, but Haskell has the best support and it has a growing community around it.
If you like graphics work, consider this book: http://www.amazon.com/Haskell-School-Expression-Functional-Programming/dp/0521644089/ It introduces Haskell from the beginning using graphics and gaming examples. You will probably need at least one other introductory Haskell book also to round out the edges.
> Laziness allows us to separate the two concerns.
Indeed, see also John Hughes : http://www.cs.chalmers.se/~rjmh/Papers/whyfp.pdf
“Since this method of evaluation runs f as little as possible, it is called “lazy evaluation”. It makes it practical to modularise a program as a generator which constructs a large number of possible answers, and a selector which chooses the appropriate one. [...] Lazy evaluation is perhaps the most powerful tool for modularisation in the functional programmer’s repertoire.”
window (10000010, 10000010) (10000015, 10000015) $ (mandelbrot 1.0)?window (10, 20) (10, 20) 0.05 $ mandelbrot?Eli: That just returns an empty list. Each pair is a range along a dimension, not a coordinate. Are you making a point about the cost of dropping all of those list elements?
Not quite sure what you are getting at with (2). If it’s the fact that we could pass the computation for single points to iteration code, then I agree that is a different way of factoring this, but it couples the gridding and constraints in the window function.
Let me clarify the two points I’m making:
windowfunction, but that’s pretty minor since it is very straightforward to do the same in a strict language.Combine the two points (avoid the cost for dropping items, abstract the resolution), and the code that you end up with is going to be nearly identical in either a lazy or a strict language. That’s why I started by saying that this is not a good example (of using a lazy language).
Just to balance things, let me point out one big advantage of a lazy language. A feature that is desirable in many languages is list comprehension. Now the thing is how do you deal with list comprehension vs iteration? In Python, for example, if you use
for i in range(1,100000000), it will actually (try to) build the huge list and then perform the iteration. As a result, strict languages might have one syntax for lists and another for loops. As another example, in PLT Scheme there is anin-rangefunction that instead of generating a list, it generates a “sequence” value, which can be used in aforexpression (and easily convertible into a list, but that’s irrelevant).But in a lazy language a list is the same as a specification of an iteration, and the equivalent of such a
rangefunction can be used either as a list or as a specification for a loop and you don’t lose any efficiency. The obvious way to see this is to see how the language deals with infinite iterations: in Pythonrangeproduces a finite range, in PLT Scheme there is ain-naturalsfunction but it produces one of these sequence values rather than a list—but only in Haskell you have the same value representing both an infinite iteration and a list.(BTW, gravatar-enabled comments are cute, but the interface should remember my email for later posts…)
It’s easier and faster to handle the separation of concerns on a slightly different level: (1) computing mandelbrot value for a single point and (2) computing some value for all points on a grid. This does not even require laziness, just higher order functions.
let map2d fn xs ys = [[fn x y | x <- xs] | y <- ys]
map2d mandelpoint [x0, x0+step .. x1] [y0, y0+step .. y1]
Using an infinite list isn’t a good idea here. Like Eli argued, computing the values far away from origin is going to be super slow due to drop taking O(x0 times y0) time. Some abstraction cost may be acceptable, but jumping from O(1) to O(N times M) is not.
http://www.haskell.org/haskellwiki/Tying_the_Knot should be better knowned.