Testing if a sequence is lazy

191 views
Skip to first unread message

Nick Brown

unread,
Jan 18, 2011, 12:22:27 AM1/18/11
to Clojure
Hi, I'm wondering if there is a good way to test if a given sequence
is lazy, and if so, how much of it has been evaluated. I know the
fact that it is lazy should be transparent, but I'm thinking in the
context of unit testing knowing that could be valuable. For instance
if you know a particular sequence could be particularly large or
expensive in certain situations, you may want your tests to assert
that it is not getting evaluated prematurely.

I suppose I could hack the generator function to cause a side effect
that I could test for, but is there an easier way?

MiltondSilva

unread,
Jan 18, 2011, 7:16:29 AM1/18/11
to Clojure
Testing for laziness seems simple:
(defn lazy? [coll]
(= (type coll) clojure.lang.LazySeq))

"For instance if you know a particular sequence could be particularly
large or expensive in certain situations, you may want your tests to
assert that it is not getting evaluated prematurely.."

That's what lazy seqs exists for. You get something that doesn't
evaluated prematurely... Or am I missing your point?

Brian Marick

unread,
Jan 18, 2011, 12:29:14 PM1/18/11
to clo...@googlegroups.com

On Jan 18, 2011, at 6:16 AM, MiltondSilva wrote:

> Testing for laziness seems simple:
> (defn lazy? [coll]
> (= (type coll) clojure.lang.LazySeq))

It's fairly easy to get other types that are (effectively) lazy. For example,

(cons 1 (map identity [1 2 3]))

is a clojure.Lang.Cons but I bet the original author would consider that just as good as a LazySeq.

> "For instance if you know a particular sequence could be particularly
> large or expensive in certain situations, you may want your tests to
> assert that it is not getting evaluated prematurely.."
>
> That's what lazy seqs exists for. You get something that doesn't
> evaluated prematurely... Or am I missing your point?

I think the original author wants to have some computation that returns a sequential but be extra-special confident that elements have not yet been been evaluated. My hunch is that type-checking won't do it. For example, I expect he'd dislike it if his computation (or the functions it used) called #'doall somewhere. However, #'doall doesn't change the type:

user> (type (doall (map identity [1 2 3])))
clojure.lang.LazySeq

If this were my problem, I'd wonder if I could make the computation accept functions. Then you could do something like this:

(fact "After the first, sprouts are not created until needed"
(let [explosive-seed (fn [& rest] (throw (Error. "Boom!")))]
(first (sprout-maker explosive-seed)) => identity-sprout?
(second (sprout-maker explosive-seed)) => (throws Error #"Boom")))

-----
Brian Marick, Artisanal Labrador
Contract programming in Ruby and Clojure
Author of /Ring/ (forthcoming; sample: http://exampler.com/tmp/ring.pdf)
www.exampler.com, www.exampler.com/blog, www.twitter.com/marick

Brian Marick

unread,
Jan 18, 2011, 2:38:34 PM1/18/11
to clo...@googlegroups.com

On Jan 18, 2011, at 11:29 AM, Brian Marick wrote:

> f this were my problem, I'd wonder if I could make the computation accept functions. Then you could do something like this:


That was a lame solution except in the special case where the first element must be computed. Here's a better solution, supposing that #'sprout-maker is known to use #'next-sprout to compute its next result. It's awkward because of a couple of not-yet-implemented features, which I'll explain after the code.

What the following code does is fake out #'next-sprout and ask Midje to complain if it's ever called.

(defn ignore-return-value [x] nil)

(fact "sprouts are lazily created"
(expect (ignore-return-value (sprout-maker 3439393)) => anything
(not-called next-sprout)))

1. Midje goes to some effort to force evaluation of LazySeqs inside
the return value of the function-under-test. That's usually what you
want, so that faked functions do what you say they should.
The only way to prevent forcing, though, is by throwing away
the return value.

2. The normal "sweet" syntax doesn't support #'not-called, so I had
to use the "semi-sweet" syntax it's built on top of. There'll someday
be a better way to say that.

Chouser

unread,
Jan 19, 2011, 12:39:03 PM1/19/11
to clo...@googlegroups.com

I've got some code that changes the way the REPL prints lazy seqs so
that it never forces the realization of anything. It prints anything
that's already been forced, but then prints "...unrealized..." for the
rest. I don't know if this is useful for unit testing, but I've found
it helpful at the REPL.

Also note that it's a complete hack -- includes big chunks of code
copied from clojure.core (hence the copyright notice), makes use of
internal clojure details that could change at any time, probably fails
in various spectacular ways. Use at your own risk. :-)

http://gist.github.com/589694

--Chouser
http://joyofclojure.com/

Reply all
Reply to author
Forward
0 new messages