`for i = 1:M, j = 1:N` discrepancy (loop vs comprehension/generator)

Well, in your comprehension the i is closer to the body of the loop, and in your loops, that’s j.

the “closest rule” only applies to the shorthand: for i in, j in. If you have two for ..., it doesn’t matter if you write them in one line or two lines, it’s the same as:

for i in
    for j in
2 Likes

A level? When I’m writing tests I’m frequently nesting like six variables and saving five levels of indentation. And I’m totally unconcerned about the loop order in that context.

A more interesting property than indentation is how break works.

You could always write

for i for j for k for l for m for n
   # one level of indentation
end end end end end end

:wink: (I actually do sometimes write nested loops like this because @threads for i, j doesn’t work.)

I hadn’t thought about that (I rarely use break). My initial thought is that the current way makes sense:

julia> for i = 1:2, j = 1:3
           println((; i, j))
           break
       end
(i = 1, j = 1)

vs

julia> for i = 1:2
           for j = 1:3
               println((; i, j))
               break
           end
       end
(i = 1, j = 1)
(i = 2, j = 1)

In the first case, there’s only one for, so break exits the only loop, but in the second case there are two fors, so break exits the for in which it resides.

2 Likes

I think the problem stems from the way Julia stores n-d arrays in memory. The odd execution order of list comprehension like [(i, j) for i = 1:2, j = 1:3] is IMO mostly an artifact of its memory configuration.

In Julia, an n-d array is stored in such a way that the adjacent elements in the first dimension are consecutive in memory. Consider an array A[i, j]. This memory configuration means that A[1, 2] is adjacent to A[2, 2], not A[1, 3]. Therefore, it becomes natural that

julia> [(i, j) for i = 1:2, j = 1:3]
2×3 Matrix{Tuple{Int64, Int64}}:
 (1, 1)  (1, 2)  (1, 3)
 (2, 1)  (2, 2)  (2, 3)

traverses i first in order to fill consecutive spots in memory. This conflicts with our expectation of loops, where the first iterator (i in for i in 1:2, j in 1:3) is expected to be slower changing compared to the ones come after it (j).

I think the best approach to this is assuming no inner-dependency between the iterators i and j when they are used to produce an n-d array. Instead, view them as an single iterator over all elements of such array.

I find @sudete’s argument to be persuasive, and I also tend to avoid for i, j loops unless I know that I don’t care about the order of iteration, because I have hardwired the [... for i, j] indexing of array comprehensions into my brain and don’t want to pollute it with a reversed convention.

How do you feel about this?

julia> for i in 1:4; for j in 1:i
           @show i, j
       end; end
(i, j) = (1, 1)
(i, j) = (2, 1)
(i, j) = (2, 2)
(i, j) = (3, 1)
(i, j) = (3, 2)
(i, j) = (3, 3)
(i, j) = (4, 1)
(i, j) = (4, 2)
(i, j) = (4, 3)
(i, j) = (4, 4)

Edit: Oops, bumped an old thread by accident again…

2 Likes