If lazy-cons makes your life easier, I think you can still have
something very much like it:

(defmacro lazy-cons [x s]
  `(lazy-seq (cons ~x (lazy-seq ~s))))

If you're interested in perf, you'd just have to write your code a bit
carefully to avoid double lazy-seq'ing the rests:

(defn map [f coll]
        (lazy-seq
          (old-map [f coll])))

where old-map is the old version of map (modified to cache the call to
"seq"?).  Does this make sense?

-Jason

P.S. thanks for updating combinatorics, I've been waiting for that.



On Feb 27, 8:10 pm, Mark Engelberg <mark.engelb...@gmail.com> wrote:
> I just finished porting my combinatorics code to the new lazy
> constructs, and I discovered some subtleties to using lazy-seq that
> were not at first apparent.
>
> To begin with, consider the two versions of map:
> The old way:
>
> (defn map
>   ([f coll]
>    (when (seq coll)
>      (lazy-cons (f (first coll)) (map f (rest coll)))))
>
> The new way:
>
> (defn map
>   ([f coll]
>    (lazy-seq
>     (when-let [s (seq coll)]
>       (cons (f (first s)) (map f (rest s))))))
>
> Let's imagine that you are using map on a collection for which it is
> very computation intensive to generate the rest, but trivial to
> generate the first.
>
> I believe that in this case, you'd get more desirable behavior from
> the old way than the new way.
>
> That's because the original lazy-cons kept the first and rest thunks
> separate.  It would force the first to get the rest, but it would NOT
> force the rest to get at the first.  So if you ask for the first, it
> wouldn't do all the rest-generating computation.  However, the new
> version uses a delayed regular cons.  So when you invoke the first of
> the sequence, both arguments of cons are evaluated, so (map f (rest
> s)) is called, and therefore (rest s) must be evaluated.
>
> If this is unclear, consider this:
> (defn a [] (do (println "a") nil))
> (def s (lazy-seq (cons (a) (a))))
> (first s)
>
> If you type this into the REPL, you'll see that the rest gets
> evaluated when you ask for the first.
>
> To get the desired separation of first and rest evaluation with the
> new lazy-seq function, you'd actually need to do something like this:
>
> (def lazier-map
>   (let [step (fn step [f coll]
>                (when-let [s (seq coll)]
>                  (cons (f (first s)) (lazy-seq (step f (rest s))))))]
>     (fn [f coll]
>       (lazy-seq (step f coll)))))
>
> Basically, I've made a helper function which uses lazy-seq to delay
> the evaluation of the rest, and then wrapped the call to the helper
> function in a lazy-seq in order to delay evaluation of the very first
> item.
>
> As a challenge, try to similarly fix up the version of filter provided
> on the lazy page so that it fully separates the evaluation of first
> and rest, thus protecting you against unnecessary evaluation if rest
> is a slow operation on coll.  I think you'll find that you end up with
> two levels of indirection, and it's extremely difficult to write it
> properly.  Post your simplest solution here.
>
> Now in both these examples, you could argue that in all likelihood,
> rest will be a fast operation.  But I chose map here because everyone
> knows the way we're supposed to write map, so it seemed like a good
> choice to illustrate my concerns without having to explain how the
> function works, etc.
>
> But this kind of problem does actually occur in practice.  It appeared
> in several places in the code I ported, because my code tends to do
> most of the work within the arguments to the recursive call.  I found
> it difficult to reason about where to place the calls to lazy-seq in
> order to achieve the separation I needed for evaluating the first and
> the rest.  I think I pulled it off correctly, but I've got to say I'm
> not crazy about how, to do the "right thing", the code ends up looking
> quite obfuscated.
>
> In summary, the new version gives you the most power to place the
> delays where you want, but it's hard to get it right.
--~--~---------~--~----~------------~-------~--~----~
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
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