doall, dorun, doseq, for

1,521 views
Skip to first unread message

Mark Volkmann

unread,
Mar 3, 2009, 10:03:48 AM3/3/09
to clo...@googlegroups.com
Does this seem like a good way to choose between doall, dorun, doseq
and for to evaluate all the items in sequences?

Ask these questions:

Do you already have the lazy sequence in a variable or do you still
need to build it?
If you already have it, use dorun or doall. Otherwise use doseq or for.
While code inside a dorun or doall could build the sequence, using
doseq and for are considered more idiomatic/readable.
Also, they provide list comprehension features such as processing more
than one sequence and filtering with :when/:while.

For example, instead of using the following to get a new sequence
where all the items are multiplied by two:
(doall (map #(* % 2) my-coll))
use this:
(for [item my-coll] (* item 2))

Are you only interested in side effects of the evaluations or do you
need to retain the results?
If you only need side effects, use dorun or doseq. If you need the
results, use doall or for.

--
R. Mark Volkmann
Object Computing, Inc.

Laurent PETIT

unread,
Mar 3, 2009, 10:12:31 AM3/3/09
to clo...@googlegroups.com
Hello Mark,

Just one point :

2009/3/3 Mark Volkmann <r.mark....@gmail.com>


Does this seem like a good way to choose between doall, dorun, doseq
and for to evaluate all the items in sequences?

Ask these questions:

Do you already have the lazy sequence in a variable or do you still
need to build it?
If you already have it, use dorun or doall. Otherwise use doseq or for.
While code inside a dorun or doall could build the sequence, using
doseq and for are considered more idiomatic/readable.
Also, they provide list comprehension features such as processing more
than one sequence and filtering with :when/:while.

For example, instead of using the following to get a new sequence
where all the items are multiplied by two:
(doall (map #(* % 2) my-coll))
use this:
(for [item my-coll] (* item 2))

I don't think it is a good example, since it conveys the idea that it could be interesting to use doall or for for mapping a coll to multiply its items.
This example, in which there is no side effect at all in the inner loop, is really typical of the use of map ! And forcing the sequence there gives you nothing (?)

Maybe a more interesting example could be something that touches global vars, or does IO, ... ?

Mark Volkmann

unread,
Mar 3, 2009, 10:16:07 AM3/3/09
to clo...@googlegroups.com

Very good point! With what I'm doing, simply multiplying items by two,
you'd want the result to be an unevaluated, lazy sequence which is why
map would be preferred.

But supposing I was doing something like you suggested where I really
do want to force the evaluation of all the items in the sequence, do
my rules of thumb make sense?

Laurent PETIT

unread,
Mar 3, 2009, 10:24:08 AM3/3/09
to clo...@googlegroups.com
2009/3/3 Mark Volkmann <r.mark....@gmail.com>

I haven't thought really hard about it, what it written seems correct to me.

But I think the first point is a little bit dangerous without a side note : "if you already have the seq, then call 'doall or 'dorun on it".
Indeed, if you're dealing with a seq that is passed around, you may end up calling dorun or doall on the seq in several places in the code, which may lead to problems if you have side effects in them !
So to prevent this risk : either the call to 'doall or 'dorun is factorized, and it will certainly be factorized near the place where the seq is generated (thus almost falling back to your second case "you don't have generated the seq yet"), either you haven't side effects at all, and you fall back to an idiomatic use of 'map or another lazy-seq generating function, without the need to call 'doall or 'dorun on them.

HTH,

--
Laurent
 

Peter Wolf

unread,
Mar 3, 2009, 10:41:17 AM3/3/09
to clo...@googlegroups.com
RE: Side Effects

What about logging? Without a debugger I use lots of print's to debug
my code... and that often produces confusing results as things may not
get evaluated in the order I expect.

For that matter, now that we have integrated Java debuggers, what does
setting a breakpoint really mean? And what does it imply about the
state of all the variables outside the current scope?

How do others think about debugging lazy code?

P

Laurent PETIT wrote:
> 2009/3/3 Mark Volkmann <r.mark....@gmail.com
> <mailto:r.mark....@gmail.com>>
>
>
> On Tue, Mar 3, 2009 at 9:12 AM, Laurent PETIT
> <lauren...@gmail.com <mailto:lauren...@gmail.com>> wrote:
> > Hello Mark,
> >
> > Just one point :
> >
> > 2009/3/3 Mark Volkmann <r.mark....@gmail.com
> <mailto:r.mark....@gmail.com>>

Chouser

unread,
Mar 3, 2009, 10:42:23 AM3/3/09
to clo...@googlegroups.com
On Tue, Mar 3, 2009 at 10:03 AM, Mark Volkmann
<r.mark....@gmail.com> wrote:
>
> Does this seem like a good way to choose between doall, dorun, doseq
> and for to evaluate all the items in sequences?
>
> Ask these questions:
>
> Do you already have the lazy sequence in a variable or do you still
> need to build it?
> If you already have it, use dorun or doall. Otherwise use doseq or for.

If you have a lazy sequence with side-effects, you almost certainly
don't want to let it out of your sight. You're likely to get very
strange behavior unless you're exceedingly careful. Most likely, if
you've got a lazy seq with side effects you should force it with dorun
or doall immediately. Use doall if you care about the values in the
produced seq, otherwise use dorun.

This means that dorun should almost always show up right next to the
form producing the lazy seq, which means doseq is very likely a better
choice, as it is more efficient and usually more succinct than dorun
combined with a lazy-seq producer.

'for' is in rather a different category, since unlike the others it
produces a lazy seq rather than forcing anything. Use 'for' when it's
a more convenient way to express the lazy seq you want than the
equivalent combination of map, filter, take-while, etc.

--Chouser

Meikel Brandmeyer

unread,
Mar 3, 2009, 10:56:11 AM3/3/09
to clo...@googlegroups.com
Hi,

Am 03.03.2009 um 16:42 schrieb Chouser:

> If you have a lazy sequence with side-effects, you almost certainly
> don't want to let it out of your sight. You're likely to get very
> strange behavior unless you're exceedingly careful. Most likely, if
> you've got a lazy seq with side effects you should force it with dorun
> or doall immediately. Use doall if you care about the values in the
> produced seq, otherwise use dorun.
>
> This means that dorun should almost always show up right next to the
> form producing the lazy seq, which means doseq is very likely a better
> choice, as it is more efficient and usually more succinct than dorun
> combined with a lazy-seq producer.

What is the use case for dorun? It returns nil, so it can itself only
be called as a side-effect. From doall and dorun, only doall makes
sense to me. It is either called immediately

(doall (map ...))

Or when giving the seq out of the hands:

(with-some resource
...
(doall the-seq))

Why should there ever be the need to call dorun?

And by the way: the output of the following code doesn't lie.

(let [the-seq (map #(* % 2) (range 100))]
(doseq [x the-seq]
(println "Just produced:" x)))

> 'for' is in rather a different category, since unlike the others it
> produces a lazy seq rather than forcing anything. Use 'for' when it's
> a more convenient way to express the lazy seq you want than the
> equivalent combination of map, filter, take-while, etc.

I must confess, I almost never used for... Maybe I should
try to use it more often.

Sincerely
Meikel

Laurent PETIT

unread,
Mar 3, 2009, 11:03:03 AM3/3/09
to clo...@googlegroups.com


2009/3/3 Meikel Brandmeyer <m...@kotka.de>

Hi,

Am 03.03.2009 um 16:42 schrieb Chouser:


If you have a lazy sequence with side-effects, you almost certainly
don't want to let it out of your sight.  You're likely to get very
strange behavior unless you're exceedingly careful.  Most likely, if
you've got a lazy seq with side effects you should force it with dorun
or doall immediately.  Use doall if you care about the values in the
produced seq, otherwise use dorun.

This means that dorun should almost always show up right next to the
form producing the lazy seq, which means doseq is very likely a better
choice, as it is more efficient and usually more succinct than dorun
combined with a lazy-seq producer.

What is the use case for dorun? It returns nil, so it can itself only
be called as a side-effect. From doall and dorun, only doall makes
sense to me. It is either called immediately

 (doall (map ...))

Or when giving the seq out of the hands:

 (with-some resource
   ...
   (doall the-seq))

Why should there ever be the need to call dorun?

If you still want to force a veeerry long seq for side effect, without fearing to face an OutOfMemory error ?

Meikel Brandmeyer

unread,
Mar 3, 2009, 11:17:15 AM3/3/09
to clo...@googlegroups.com
Hi Laurent,

Am 03.03.2009 um 17:03 schrieb Laurent PETIT:

>> Why should there ever be the need to call dorun?
>
>
> If you still want to force a veeerry long seq for side effect,
> without fearing to face an OutOfMemory error ?

(let [some-loooong-seq ....]
(dorun some-loooong-seq)
...)

How does dorun help here? In such a scenario
dorun only helps, if you don't hold onto the head.
But that means you loose the seq immediately
without using the return value at all. So you made
the seq purely for side-effects. And at that point
I boldly claim you should have used doseq in the
first place....

Sincerely
Meikel

Chouser

unread,
Mar 3, 2009, 11:23:09 AM3/3/09
to clo...@googlegroups.com
On Tue, Mar 3, 2009 at 10:56 AM, Meikel Brandmeyer <m...@kotka.de> wrote:
>>
>> This means that dorun should almost always show up right next to the
>> form producing the lazy seq, which means doseq is very likely a better
>> choice, as it is more efficient and usually more succinct than dorun
>> combined with a lazy-seq producer.
>
> What is the use case for dorun? It returns nil, so it can itself only
> be called as a side-effect.

This was kind of my point. In every case I can think of at the
moment, I would prefer doseq over dorun.

>  (let [the-seq (map #(* % 2) (range 100))]
>    (doseq [x the-seq]
>      (println "Just produced:" x)))

So here's an example of where you could use dorun.

(dorun (map #(println "Just produced: " (* % 2))
(range 100)))

But I think what you had was at least as clear. Though there's no
need for 'map' if you're going to use doseq:

(doseq [x (range 100)]
(println "Just produced:" (* x 2)))

And no need for doseq if you're using a simple range:

(dotimes [x 100]
(println "Just produced:" (* x 2)))

>> 'for' is in rather a different category, since unlike the others it
>> produces a lazy seq rather than forcing anything.  Use 'for' when it's
>> a more convenient way to express the lazy seq you want than the
>> equivalent combination of map, filter, take-while, etc.
>
> I must confess, I almost never used for... Maybe I should
> try to use it more often.

I like 'for' when I need nested behavior:

(for [x '(a b c), y '(d e f)]
[x y])

vs.

(mapcat (fn [x] (map #(vector x %)
'(d e f)))
'(a b c))

Of course it also does handy things with :when, :while, and :let, as
does doseq.

--Chouser

Laurent PETIT

unread,
Mar 3, 2009, 11:26:21 AM3/3/09
to clo...@googlegroups.com
Correct. It seems that we are agreeing that we agree :-)

2009/3/3 Meikel Brandmeyer <m...@kotka.de>

Mark Volkmann

unread,
Mar 3, 2009, 2:54:00 PM3/3/09
to clo...@googlegroups.com
On Tue, Mar 3, 2009 at 9:42 AM, Chouser <cho...@gmail.com> wrote:
>
> If you have a lazy sequence with side-effects, you almost certainly
> don't want to let it out of your sight.  You're likely to get very
> strange behavior unless you're exceedingly careful.  Most likely, if
> you've got a lazy seq with side effects you should force it with dorun
> or doall immediately.  Use doall if you care about the values in the
> produced seq, otherwise use dorun.
>
> This means that dorun should almost always show up right next to the
> form producing the lazy seq, which means doseq is very likely a better
> choice, as it is more efficient and usually more succinct than dorun
> combined with a lazy-seq producer.

I agree that using doseq instead of dorun results in code that is
easier to read. I don't understand though why it is more efficient.
They both walk the entire sequence, neither holds onto the head and
both return nil. Why is doseq more efficient than dorun?

Chouser

unread,
Mar 3, 2009, 9:30:15 PM3/3/09
to clo...@googlegroups.com
On Tue, Mar 3, 2009 at 2:54 PM, Mark Volkmann <r.mark....@gmail.com> wrote:
>
> I agree that using doseq instead of dorun results in code that is
> easier to read. I don't understand though why it is more efficient.
> They both walk the entire sequence, neither holds onto the head and
> both return nil. Why is doseq more efficient than dorun?

A simple doseq does run about as fast as dorun on the same seq. Bus
something like this would be more likely (let's pretend inc has useful
side-effects):

(defn f [] (dorun (map #(inc %) (range 10000000))))

(defn f2 [] (doseq [i (range 10000000)] (inc i)))

The results would be the same, but dorun requires 'map', which creates
another seq. 'doseq' is able to accomplish the same work with less
allocation.

user=> (time (f))
"Elapsed time: 1685.796066 msecs"

user=> (time (f2))
"Elapsed time: 531.730209 msecs"

--Chouser

Reply all
Reply to author
Forward
0 new messages