Skip to content

Proposal: Rename for to each #7826

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ghost opened this issue Jan 19, 2021 · 15 comments
Closed

Proposal: Rename for to each #7826

ghost opened this issue Jan 19, 2021 · 15 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@ghost
Copy link

ghost commented Jan 19, 2021

Despite its rejection, #358 continues to be requested. I think the problem is that the name for evokes C-style ranged iteration, so people are surprised when it is exclusively for structural iteration. We could remedy that by choosing a more accurate name. each is only one possibility: it could be foreach, iterate, lane if we're feeling frisky -- but I'll leave the bikeshedding to the comments 😉. The remainder of this issue shall be musings on related subjects, and may be skipped if only a technical evaluation of the proposal is desired.

  • A case against ranged iteration, so as to finally shut up the people still preaching for Proposal: for on ranges #358. As Andrew has pointed out many times, you can already do it with while. The laziest way involves leaking a local to the enclosing scope, but the lack of shadowing means that any potential unforeseen behaviour will probably be a compile error instead. So, in the worst case, the author of a ranged iteration has to do the awkward nested block thing. However, I personally believe this to be a benefit -- it makes the author reconsider if they really want a ranged iteration, or if what they actually want is a structural iteration, which 9 out of 10 times, it is. Continuing the theme of operant-conditioning our users.

  • This proposal was born of a discussion on Discord with @ifreund, in which the idea was also raised of making the while clause optional and renaming while to loop so it wouldn't look weird without a clause. Upon further consideration, though, I think this is a mistake. Currently, while (true) looks just a bit strange, so the developer is led to think of a sensible condition for the loop, rather than leaving it off and conditionally breaking, which is much harder to unroll. On a more meta level, unconditional loop may be seen as "simpler" than conditional "loop" and thus be taught first, amplifying the previous problem as well as potentially leading to confusion later, when the learner wonders why a false condition runs an else branch but a break doesn't, when they've been led to believe that the two were semantically identical.

  • Structural iteration has numerous well-known advantages, hence my stance that it should be encouraged where possible. However, there is one case where it shares many deficiencies with its ranged counterpart: SoA data. This involves the extraction and use of elements of multiple arrays at once, which there is currently no way to express directly; the best we can do is capture the index of one array and use it to access the others, which, unless we're very careful, loses many of the guarantees and optimisation opportunities that make structural iteration worthwhile in the first place. Proposal: Multi-object for loops #7257 exists to remedy this.

@ifreund
Copy link
Member

ifreund commented Jan 19, 2021

In my opinion, each is a strictly better name for what zig's current for loop is. Renaming such a commonly used keyword would cause significant churn though so I'm not certain it would be worth it. Depends how dedicated we are to reaching the "global maximum" I guess.

With regards to the while (true) discussion, the original issue brought up was that an else clause is required on while (true) loops which break with a value despite the fact that the loop condition is never false. Making this else optional if the the while condition is comptime-known to be true would be a reasonable solution, though I'm not a fan of the special casing. The only alternative I see to such special casing or forcing the use of else unreachable; is a condition-less loop, which of course has its own problems as @EleanorNB has explained above.

@ghost
Copy link
Author

ghost commented Jan 19, 2021

Oh yeah, I was going to write that up too, wasn't I? One sec.

@Guigui220D
Copy link

Why not foreach? Sounds more like a loop to me than "each"

@ghost
Copy link

ghost commented Jan 19, 2021

Isn't the ranged iteration issue sort of orthogonal to the choice of keyword? The main problem is that most modern languages embrace the view that for loops should support all iterable objects (arrays, hash maps, ranges, custom types, etc.), while Zig only allows structural iteration over arrays and slices. While this remains so, there will always be complaints from people coming from Rust, Julia, younameit that Zig makes ranged iteration unnecessarily difficult. Calling the for loop iterate or foreach is unlikely to change that, IMHO.

@ghost
Copy link
Author

ghost commented Jan 19, 2021

@Guigui220D It's not necessarily a loop though -- it might be vectorised or tiled by the optimiser.

@zzyxyzz The name is not the only factor, but I believe it is significant -- it may seem like false advertising. We will not simply support other kinds of iteration, either, for established and well-documented reasons.

@ifreund
Copy link
Member

ifreund commented Jan 19, 2021

As I see it, people have very specific assumptions of what a for loop does due to the keyword's history in C and other languages. These preconceptions are what cause the confusion, not the semantics of zig's for loop themselves which are quite straightforward IMO.

As a name, I think each is both concise and accurate but also doesn't have a long-standing, conflicting meaning in the some of the most widely used programming languages. Off the top of my head, the only other language I know of that uses each is janet, which also uses it for simple structured iteration.

@cryptocode
Copy link
Contributor

The laziest way involves leaking a local to the enclosing scope, but the lack of shadowing means that any potential unforeseen behaviour will probably be a compile error instead.

Accidentally reusing variables in loop conditions at the same nesting level is/was a common source of subtle bugs in C89, which is why C99 allowed declarations in expr-1 of the for-loop, restricting the scope appropriately. No shadowing occurs here, which makes it brittle when refactoring.

So, in the worst case, the author of a ranged iteration has to do the awkward nested block thing

Looking at existing code, including std, this doesn't seem to be what people actually do 🤔 (because it's awkward/easy to forget?)

In pursuit of a better language than C, I think this needs to be fixed. I suggested an optional initialization clause for the while statement, but not sure what syntax would work with the grammar goals.

@ghost
Copy link

ghost commented Jan 19, 2021

@ifreund, @EleanorNB,

As I see it, people have very specific assumptions of what a for loop does due to the keyword's history in C and other languages. These preconceptions are what cause the confusion, not the semantics of zig's for loop themselves which are quite straightforward IMO.

I think it's inaccurate to assume that people use ranged iteration only out of sheer habit, and that the frustration with Zig's for is only because it does something different than C's for. Despite claims to the opposite, there are plenty of situations where iterating over a range is the natural thing to do, and what bothers people is that Zig doesn't provide a first-class way of doing that. Under what keyword Zig doesn't provide that functionality is of lesser importance.

BTW, I'm not necessarily arguing against each, I'm just saying that it does not address the problem of ranged iteration in any meaningful way.

@ifreund
Copy link
Member

ifreund commented Jan 19, 2021

@cryptocode this has been rejected before in #5070. As mentioned there, a possible (partially social) fix would be to make zig fmt accept something like the following:

{var i: usize = 0; while (i < 10) : (i += 1) {
    // body
}}

though I'd personally prefer to have the option of this variant as well:

{var i: usize = 0;
while (i < 10) : (i += 1) {
    // body
}}

@cryptocode
Copy link
Contributor

cryptocode commented Jan 19, 2021

@ifreund but that relies on people scoping manually in the first place, which doesn't seem to be the case (easy to forget/awkward, especially when nesting). The reason for the rejection was basically "just remember to put braces there". This will be a significant source of bugs once people start refactoring large code bases.

@kyle-github
Copy link

If there was a way to make each a function attached to an array or slice then there wouldn't be any confusion. I do not see how to do that without something like the macro proposal (#6965) though. Unless it was part of the language itself.

@faraazahmad
Copy link

faraazahmad commented Jan 19, 2021

@ifreund Ruby also uses a .each over iterable data types. I think for is more intuitive and newcomer friendly because of the history of the for loop. C++ has a similiar kind of loop like so:

for(type variable_name : vector_name)
{
    loop statements
    ...
}

@kliberty
Copy link

@ifreund but that relies on people scoping manually in the first place, which doesn't seem to be the case (easy to forget/awkward, especially when nesting). The reason for the rejection was basically "just remember to put braces there". This will be a significant source of bugs once people start refactoring large code bases.

It's all good to say people should do the right thing, but to borrow a phrase I've seen mentioned a few times, this an easy-to-trigger footgun. Most of Zig seems designed to eliminate categories of common bugs, yet this is reintroducing one from C89.

I suppose an alternate solution could be for the compiler to emit a warning when a loop variable is reused.

@andrewrk andrewrk added the proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. label Jan 19, 2021
@andrewrk andrewrk added this to the 0.8.0 milestone Jan 19, 2021
@ghost
Copy link

ghost commented Jan 20, 2021

IMHO, foreach is a better name than each, because

var numbers = [_]usize{ 1, 2, 3, 4, 5 };

foreach (numbers) |*number| {
    number.* += 1;
}

is more readable than

var numbers = [_]usize{ 1, 2, 3, 4, 5 };

each (numbers) |*number| {
    number.* += 1;
}

@andrewrk
Copy link
Member

andrewrk commented Jan 20, 2021

Closing in favor of status quo. for is an adequate description of what it is doing, and there is value in having the core keywords (switch, if, for, while, etc) be the same as C and similarly popular languages, both in terms of familiarity as well as less @"each" happening when zig and other languages talk to each other.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

7 participants