Extra token, on same line as for loop start, is valid?


#1

The following is considered valid, but should it be?

for i in 1:3 4
   print("hi")
end
# output: "hihihi"

This enables one-line for expressions without semicolons, of course:

for i in 1:3 print("hi") end

I encountered this potential issue when I temporarily replaced the upper index of the range but forgot to comment out the old range.

Perhaps semicolons should be required if attempting to place another statement on the same line as a for loop initiator? IMO would make the one-line code more clear.


#2

Bumped, with edits for clarity.


#3

FWIW, I would prefer if semicolons were only required when there is an ambiguity on the parser level (not for protecting against user mistakes).

That said, for one-line loop bodies I would either go for

foreach(_ -> print("hi"), 1:3)

or

for _ in 1:3
    print("hi")
end

as squeezing a for or if onto one line is rarely worth it IMO.


#4

I understand. In that case, would it make any sense, and not break anything, to require a return character after the for loop range and similar block declarations? (apologies if I’m abusing terminology)

Or maybe a warning would be useful?


#5

But it would break existing code.

While I don’t use one-liners, some people do and I don’t feel like enforcing my own style upon them (for one thing, my own style underwent quite a few changes already :wink:). So I don’t think this should be invalid syntax.

However, this would be the perfect job for a linter, eg

for a in 1:10 print(a + 1)
    print(a + 2)
end

could warn because it is mixing the two styles. Or one could ask the linter to warn about

for a in 1:10 print(a + 1) end

if an internal style guide for an organization considers it undesirable.


#6

So this linter (new term for me) would be an add-on, probably as part of e.g. Juno?


#7

See eg


#8

Perhaps it would have made sense to require semicolons. I don’t know why it was done this way, there’s probably a good reason. Several other keywords work this way too, such as if and while. However, let requires a semicolon:

julia> let a = 3 println(a) end
ERROR: syntax: let variables should end in ";" or newline

What’s more surprising to me is that not even a space is required before an end statement. This is perfectly valid:

julia> a = 3; while a > 0 println(a); global a-=1end
3
2
1

As for linter and warnings, looking again at the code that troubled you: for i in 1:3 4, it might be more effective to detect and warn about that independent 4, than requiring semicolons after keywords. (Or are there cases where a single number like that would make sense, unless when used as a return value?)

What I think can be said with certainty is that changing this would break an enormous amount of code, and would likely have to be done over a series of releases (like v0.7 and v1.0). I have used this syntax many times, especially when answering questions on this forum to keep my replies more compact – here’s an example:

@btime (s = 0.0; @simd for i in 1:10^9 s += i * 1.23 end; s)

There’s also plenty of existing code out there using this syntax, here’s an example in Julia’s sparse matrix library:

for i in (k + 2):(n + 1) colptr[i] = (k + 1) end

#9

As for linter and warnings, looking again at the code that troubled you: for i in 1:3 4 , it might be more effective to detect and warn about that independent 4 , than requiring semicolons after keywords. (Or are there cases where a single number like that would make sense, unless when used as a return value?)

The number was just for the example. In my real case it was a variable name, and I had overlooked it as a mistake when tracking down why it wasn’t working. E.g.:

for i in 1:3 nvalues # supposed to be 1:nvalues, temporarily replaced with constant
    #dostuff
end

#10

Well, same thing – would it make sense to just have an unused variable in your code? (Except when used as a return value, in which case it’s not unused of course.)

Just thinking whether it’d be better for a linter to focus on this, than the keyword syntax. Or perhaps both.


#11

Possibly because the number of assignments following a let can vary?


#12

Maybe? But could you not argue that it’s the same with for loops?

julia> for a=1:2, b=3:4 println(a*b) end
3
4
6
8

#13

I think for let, it leads to ambiguous syntax. See this commit from the dawn of time.


#14

Nice find! But I don’t see what the ambiguity is, and how it’s different from for loops, do you? Since let assignments must be comma separated (also at the time of that commit), wouldn’t the only way to parse let x = 1 y = 2 be the let statement let x = 1 followed by the assignment y = 2? Which is confusing and could lead to bugs, but so could for loops:

julia> for a=1:2, b=3:4
           println("$a, $b")
       end
1, 3
1, 4
2, 3
2, 4

julia> for a=1:2 b=3:4
           println("$a, $b")
       end
1, 3:4
2, 3:4 

#15

I think the ambiguity comes from the fact that the let syntax allows multiple assignments of the form

let a = b = 1
    println("a = $a, b = $b")
end

where as the for syntax does not.


#16

I am not sure. I could not find any unit tests for the let parsing ambiguity. Perhaps @jeff.bezanson could clarify this.


#17

I’m not really sure, but my best guess is that let predated for loops with multiple ranges. With a for loop, you knew that anything after the first assignment was in the loop body. But

let x = 1 y = 2
    ...
end

would be highly misleading. Basically, you expect let to have multiple variables and a for loop to have one. But yes, this is now inconsistent. Oh well.


#18

Could making it consistent qualify as a minor change? Otherwise we should have a 2.0 issue to track changing this.


#19

I suspect changing this is likely to break a bunch of code. It’s easy to imagine people writing 1-line for loops like the example here. We can change it in 2.0 but it’s a slap-on-the-wrist kind of thing.