Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Puzzled: Clojure for loop with :while -> unexpected behaviour?

Tags:

clojure

I've been learning Clojure and puzzled by the following:

user=> (for [a (range 1 4) b (range 1 4)] [a b])
([1 1] [1 2] [1 3] [2 1] [2 2] [2 3] [3 1] [3 2] [3 3]); _no surprise here_

Let's add :while (not= a b), I expect to see an empty list as the loop should stop if the condition is false. In this case it's the very first item where a=b=1. Let's see:

user=> (for [a (range 1 4) b (range 1 4) :while (not= a b) ] [a b])

([2 1] [3 1] [3 2]) ; _surprise!_

Changing :while to :when to filter out (= a b) pairs

user=> (for [a (range 1 4) b (range 1 4) :when (not= a b) ] [a b])
([1 2] [1 3] [2 1] [2 3] [3 1] [3 2]); _expected_

Could anyone explain why (for [ ... :while ..] ...) behaves like this?

I'm using Clojure 1.3 on OS X.

Thank you and apologize for the lack of formatting. This is my virgin post on StackOverflow.

like image 344
jbear Avatar asked Jan 11 '12 08:01

jbear


2 Answers

Let's look at each iteration.

a = 1
  b = 1 -> a == b, break because of while

a = 2
  b = 1 -> a != b, print [2 1]
  b = 2 -> a == b, break because of while

a = 3
  b = 1 -> a != b, print [3 1]
  b = 2 -> a != b, print [3 2]
  b = 3 -> a == b, break because of while
like image 129
Mikita Belahlazau Avatar answered Oct 31 '22 07:10

Mikita Belahlazau


The :while condition in for only terminates the inner-most loop. I use for all the time, but :while so rarely that I never realized this; thanks for the great question!

Sadly I think the best you can do is wrap a take-while around the for, since you want a "global" stop-counter on the output sequence, not a stop-counter on one of the input sequences you're iterating over. For example:

(->> (for [a (range 1 4)
           b (range 1 4)]
       [a b])
     (take-while (fn [[a b]] (not= a b))))

()
like image 31
amalloy Avatar answered Oct 31 '22 08:10

amalloy