> 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
> 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.
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. :-)
--Chouser
http://joyofclojure.com/