How to loop over several sequences in parallel for side-effects?

879 views
Skip to first unread message

joachim

unread,
Jan 20, 2012, 8:18:28 AM1/20/12
to Clojure
Hi All,

Here is a simple problem for which I nevertheless can't seem to find
the right solution: How to run over several sequences in parallel for
side-effects? Here is one way:

(dorun (map some-fn-with-side-effects sequence-1 sequence-2))

However, I was told that the form "(dorun (map ... ))" indicates that
doseq should be used instead because it is faster, and I can use
speed. I think that this is not possible however because doseq only
allows to loop over a single sequence at a time? I was wondering what
is the idiomatic clojure way in this case?

Thanks! Jm.

Lars Nilsson

unread,
Jan 20, 2012, 2:41:26 PM1/20/12
to clo...@googlegroups.com

You know that (map f seq1 seq2) means call f with one argument from
seq1 and one from seq2 in turn, until one of the sequences has been
exhausted?

Does

=> (map #(vector %1 %2) [1 2 3] ['a 'b 'c])
([1 a] [2 b] [3 c])

Seem like the result you would expect or not, when you talk about "run
over several sequences in parallel"?

Lars Nilsson

Meikel Brandmeyer

unread,
Jan 20, 2012, 3:40:41 PM1/20/12
to clo...@googlegroups.com
Hi,

to add to Lars answer:

(doseq [[a b c] (map vector s1 s2 s3)]
(side-effect-fn a b c))

This should do the trick.

Sincerely
Meikel

Steve Miner

unread,
Jan 20, 2012, 3:57:35 PM1/20/12
to clo...@googlegroups.com

On Jan 20, 2012, at 2:41 PM, Lars Nilsson wrote:

> => (map #(vector %1 %2) [1 2 3] ['a 'b 'c])
> ([1 a] [2 b] [3 c])

Sorry if I'm drifting a bit off topic, but I just wanted to point out that it's convenient to use just the function name if the arguments are already in the appropriate order. Also, it is sometimes convenient to quote a vector rather than the individual elements. For example, the following is an equivalent expression:

(map vector [1 2 3] '[a b c])

Of course, wrapping the expression in dorun is necessary if you care about forcing side-effects.

Alan Malloy

unread,
Jan 20, 2012, 3:57:53 PM1/20/12
to Clojure
But I don't see any reason why this would be faster than (dorun (map
side-effect-fn s1 s2 s3)). You're creating and then dismantling a
three-element vector at every iteration to no purpose.

Lars Nilsson

unread,
Jan 20, 2012, 4:06:05 PM1/20/12
to clo...@googlegroups.com
On Fri, Jan 20, 2012 at 3:57 PM, Steve Miner <steve...@gmail.com> wrote:
>
> On Jan 20, 2012, at 2:41 PM, Lars Nilsson wrote:
>
>>  => (map #(vector %1 %2) [1 2 3] ['a 'b 'c])
>>  ([1 a] [2 b] [3 c])
>
> Sorry if I'm drifting a bit off topic, but I just wanted to point out that it's convenient to use just the function name if the arguments are already in the appropriate order.  Also, it is sometimes convenient to quote a vector rather than the individual elements.  For example, the following is an equivalent expression:
>
> (map vector [1 2 3] '[a b c])

I had something else going on inside #() for a while, and later just
switched to vector. Oops.

Lars Nilsson

Meikel Brandmeyer (kotarak)

unread,
Jan 23, 2012, 9:43:24 AM1/23/12
to clo...@googlegroups.com
And (dorun (map )) is creating a cons with a random value (most likely nil) and traverses it and throws it away at every sequence step. YMMV.

Armando Blancas

unread,
Jan 23, 2012, 11:40:49 AM1/23/12
to Clojure
Can you point to where that's happening? I can only see an iteration
of next over the sequence returned by map.

On Jan 23, 6:43 am, "Meikel Brandmeyer (kotarak)" <m...@kotka.de>
wrote:

Jeff Palmucci

unread,
Jan 23, 2012, 2:43:15 PM1/23/12
to clo...@googlegroups.com
Shameless plug: If you want to do this type of iteration efficiently, try my library at https://github.com/jpalmucci/clj-iterate

user> (iter {for x in '(1 2 3)}
{for y in '(a b c)}
(println x y))


1 a
2 b
3 c

nil
user>

Expands into a fast loop/recur form. No intermediate data structures

> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@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+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

Meikel Brandmeyer

unread,
Jan 23, 2012, 4:40:11 PM1/23/12
to clo...@googlegroups.com
Hi,

Am 23.01.2012 um 17:40 schrieb Armando Blancas:

> Can you point to where that's happening? I can only see an iteration
> of next over the sequence returned by map.

Exactly. And what does (map f xs) return? (cons (f (first xs)) (map f (rest xs))). Each such a cons is created for one step of the input sequence(s). The fact that you don't use the first part of the cons, does not mean it's not created.

Sincerely
Meikel

Meikel Brandmeyer

unread,
Jan 23, 2012, 4:44:17 PM1/23/12
to clo...@googlegroups.com
Ah. Nevermind.

Alan Malloy

unread,
Jan 23, 2012, 6:10:34 PM1/23/12
to Clojure
(dorun (map f xs ys zs)) creates and discards a cons for each
iteration, no argument there. But its first element is very cheap:
just the result of f, which you had to compute anyway.

(doseq [[x y z] (map vector xs ys zs)] (f x y z)) similarly creates a
cons for each iteration. But its value, rather than being the result
of f, is a vector constructed simply for bookkeeping purposes. Then
before you can call f you must tear apart that vector to get at the
pieces. If performance (either amount of garbage or number of
operations) were the only factor, I can't see any way that this could
be better than the dorun/map approach.

On Jan 23, 6:43 am, "Meikel Brandmeyer (kotarak)" <m...@kotka.de>
wrote:

joachim

unread,
Jan 23, 2012, 6:51:18 AM1/23/12
to Clojure
First: Thanks all for your thoughts.

Second: I have the same question as Allen (why would the doseq variant
be faster in this case?)

Finally: so I guess that what I did was also OK then?

Jm

Armando Blancas

unread,
Jan 24, 2012, 11:24:48 AM1/24/12
to Clojure
Well, you can always use the (time) macro and pick what runs faster:

(dorun (map some-fn-with-side-effects sequence-1 sequence-2))

(doseq [x (map some-fn-with-side-effects sequence-1 sequence-2))])

(doseq) could be faster in some cases because its implementation uses
chunked sequences. Now, (doseq) can take multiple sequences, but that
may not be what you want:
user=> (doseq [x '(1 2) y '(3 4)] (println x y))
1 3
1 4
2 3
2 4
Reply all
Reply to author
Forward
0 new messages