ehanneken <ehanne...@pobox.com> writes:

> I spent a long time debugging some Clojure code yesterday.  The
> essence of it looked similar to this:
>
> (defn items []
>   (mapcat expensive-function (range 0 4000 100)))
>
> . . . (take 5 (items)) . . .

I tried to distill the problem down by defining a non-chunking range
function (a simple variant), then working outward from there to see what
else is evaluating a surprising number of times.

,----
| (defn non-chunked-range
|   [start end]
|   (prn (format "In non-chunked-range with %d and %d." start end))
|   (lazy-seq
|    (when-not (= start end)
|      (cons start (non-chunked-range (inc start) end)))))
`----

Note that `mapcat` is defined in terms of `map` and `concat`. First
let's confirm that `map` is not eager:

,----
| ;; Draws one, and evaluates lazy sequence function twice:
| (take 1
|       (map #(list %)
|            (non-chunked-range 0 10)))
`----

Experimenting with the argument to `take` shows that the lazy sequence
function is evaluated as expected: a number of times equal to the
argument plus one for the terminal case (n + 1).

Now add `concat` into the mix to make sure it's not eager:

,----
| ;; Draws two, and evaluates lazy sequence function three times:
| (concat (take 2 (non-chunked-range 0 10)))
`----

That works as expected. Now add `apply` to `concat` as `mapcat` does to
flatten the input lists:

,----
| ;; Draws one, and evaluates lazy sequence five times:
| (take 1
|       (apply concat
|              (map #(list %)
|                   (non-chunked-range 0 10))))
`----

Whoah! Where did the extra three evaluations of the lazy sequence
function come from? Note that this one calls on the function /five/
times.

Here is the mapping of the argument to `take` and the number of times
the function is called:

 take   calls
 ====   =====
 0      5  
 1      5
 2      5
 3      5
 4      6 
 5      7
 ...
 n      n + 2

I read the source for `concat`, but I don't see what it's doing to force
the extra evaluations both below four arguments and the extra one
(yielding n + 2) with four or more arguments. What's responsible for
this difference in behavior?

-- 
Steven E. Harris

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to