Metaprogramming with list comprehensions

I feel like I’m always writing [foo(a,b,c) for a, b, c in 1:n] when I mean [foo(a,b,c) for a in 1:n, b in 1:n, c in 1:n], so I’d like to add something to my startup.jl to make it so.

I feel like I’ve seen conflicting information about the ease / feasibility of things like this, so figured before I get too deep I’d ask for resources / suggestions over here

I think it’s worth learning the difference, actually.

  1. for (a,b,c) in collection implies that collection contains triples that look like (a,b,c).
  2. for a in A, b in B, c in C are 3 nested loops: first you loop over elements of A, then within this loop you loop over B, then within that loop, loop over C.

This is standard Julia syntax, you’ll find it in the wild and will get confused if you write a macro that modifies its meaning.

3 Likes

then is there some other syntax for perfect square/cube/etc matrices? I feel like writing the same thing three times is the wrong way. in your example even you give A B and C as separate objects

Ah, then you’re probably looking for:

for (a, b, c) in Iterators.product(1:n, 1:n, 1:n)

This still requires typing 1:n three times, though.

You can make a small helper function though

cross(it, n) = Iterators.product((it for _ in 1:n)...)

which allows to write cross(1:n, 3) instead of Iterators.product(1:n, 1:n, 1:n).
If it were not type piracy, overloading (1:n)^3 would also be an option.

3 Likes

I spotted that .. has the same precedence as : and left associativity which made me think that iter..n = cross(iter, n) would let me 1:n..3, but it actually results in a parse error. (even with spaces added, so unrelated to .3.)

am I misunderstanding something about the precedence table? why does this need parens?
I tried swapping the order as well in case I just had the wrong direction, but got the same result

using <| works l expected it to [a for a in (1:n)<|(3)] ; going one level further down to tries to reach around the whole comprehension ([a for a in 1:n)∷(3]) and going one level up to ++ breaks the range apart [a for a in 1:(n)++(3)], which all makes sense; but 1:n..3 doesn’t seem to be equivalent to either (1):(n..3) or (1:n)..(3).

It seems that .. is not associative at all, contrary to what’s stated in the documentation:

julia> 1..2..3
ERROR: ParseError:
# Error @ REPL[1]:1:5
1..2..3
#   └─┘ ── extra tokens after end of expression

In contrast, : is associative. It takes three arguments at once, if possible:

julia> dump(:( 1:2:3:4:5:6 ))
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol :
    2: Expr
      head: Symbol call
      args: Array{Any}((4,))
        1: Symbol :
        2: Expr
          head: Symbol call
          args: Array{Any}((4,))
            1: Symbol :
            2: Int64 1
            3: Int64 2
            4: Int64 3
        3: Int64 4
        4: Int64 5
    3: Int64 6
1 Like

so does that mean this is a bug?
or is there some other reason .. can’t be associative?

when I did ? .. I didn’t see anything pop up; is there some common use that prohibits this?

Could be. I hope some expert weighs in.

This is simply because .. has no asscociated methods in Base. If you add a docstring, say with "docstring for `..`" x..y = x:y, then it will show up when you say ?...

1 Like

a
yeah, I understood that; I was more saying `it’s not a symbol in any of the packages I currently happen to have open, is it used commonly anywhere else?`

looks like the other ones at prec-colon all have that same problem
… ⁝ ⋮ ⋱ ⋰ ⋯ .… .⁝ .⋮ .⋱ .⋰ .⋯

looks like maybe it was just forgotten?

the range was explicitly defined because it can take 3 arguments, but I don’t see anything for other prec-colon stuff. I think it needs a

(define (parse-colon s) (parse-RtoL s parse-expr is-prec-colon?))

and for the parse-expr on L859, L865, and L880 to be parse-colon

idk how it fits into the rest of the codebase but that would at least make it look like the other precedences

should this be brought into a new more appropriately named thread now?