Graphing Program: Lazy Evaluation

When the program draws the graph, there is one other item that needs to be passed to all the drawing functions: the canvas’s drawing context. Rather than creating another parameter, this code creates a map that has the context and the conversion function in it. This code summarizes what has been done up to this point and adds the code to set everything up.

Here is a canvas whose id is graphCanvas:

This canvas is where the graph will go.

And here is the code to draw the axes as a series of lines that connect points:

First, notice that I put (println) calls in lines 5 and 9 to see what is going on. Second, line 2 uses associative destructuring to extract the context and conversion function from the map. Third, when you run the code—the lines don’t appear. What went wrong?

Lazy Evaluation

What is happening is that the map function produces results only when some part of the program needs that sequence. In every other usage of map that you have seen before, there has been something to consume the sequence that map produces. Either it’s output to the screen or part of some other computation. In this case, however, the function that is mapped over has only side effects (creating the canvas path) and doesn’t have any consumer. Nobody needs its results, so it is never called.

This property of lazy evaluation is often quite useful. The (range) function creates an sequence of numbers; for example, (range 5 9) creates the sequence (5 6 7 8), and (range 2 12 2) produces (2 4 6 8 10). If you don’t give any numbers, (range) produces the sequence from 0 to infinity. Let’s say you wanted the first five elements of this infinite sequence. You would write (take 5 (range)). If it weren’t for lazy evaluation, Clojure would have to completely evaluate the range before doing the take, and evaluating an infinite sequence would take a very long time. Because of lazy evaluation, the take requires only five elements, so those are the only ones that range computes.

While lazy evaluation can be useful in many cases, it isn’t useful in the graph—you want the whole sequence computed, whether anyone consumes it or not. If you only have side effects, dorun will force evaluation of the entire sequence and throw away the results. If you do have results, doall will force evaluation of the entire sequence and return the result.

So, to fix the problem with the axes, go to the graphing example, and on line 7, put (dorun before (map, and add one extra parenthesis at the end of line 10. Now when you run the program, the map will be completely evaluated and you will see the axis lines.

Next Section - Graphing Program: Completion