$ ruby --parser=parse.y -ve '[1].each { p it; [5].each { p it } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) [x86_64-linux]
1
5
$ ruby --parser=parse.y -ve '[1].each { p _1; [5].each { p _1 } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) [x86_64-linux]
-e:1: numbered parameter is already used in
-e:1: outer block here
[1].each { p _1; [5].each { p _1 } }
ruby: compile error (SyntaxError)
The behavior is inconsistent between it and _1.
Side note about mixing `_1` and `it`, which seems good
As an aside, mixing _1 and it is allowed, I think this is good, they are different things so there is not much confusion there:
$ ruby -ve '[1].each { p _1; [5].each { p it } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) +PRISM [x86_64-linux]
1
5
$ ruby -ve '[1].each { p it; [5].each { p _1 } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) +PRISM [x86_64-linux]
1
5
Prism's bug, moved to: https://github.com/ruby/prism/issues/3291
$ ruby -ve '[1].each { p it; [5].each { p it } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) +PRISM [x86_64-linux]
1
5
$ ruby -ve '[1].each { p _1; [5].each { p _1 } }'
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) +PRISM [x86_64-linux]
1
1
Notice the inconsistency, it uses the innermost block, _1 uses the outermost block.
I think _1 semantics are slightly better, at least _1 behaves like a normal local variable declared in the outer block then.
Note that on 3.3.5 it was forbidden to nest _1 which I think might be good for clarity/avoiding ambiguity:
$ ruby -ve '[1].each { p _1; [5].each { p _1 } }'
ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux]
-e:1: numbered parameter is already used in
-e:1: outer block here
[1].each { p _1; [5].each { p _1 } }
ruby: compile error (SyntaxError)
One is an incompatibility with Prism's handling of _1. I think it should be handled as an error like parse.y. Especially when parsed with the 3.3 version's syntax, there is no other choice but an error:
The other problem is how to interpret it. I think Ruby master's it behavior is good. I have experienced mistakes of a name conflict issue of a local variable in a different scope, but not as often. However, just adding a read from it outside of a block only changes the meaning of inner it, which I think would increase the frequency of mistakes very much.
I think _1 semantics are slightly better, at least _1 behaves like a normal local variable declared in the outer block then.
Yeah but _1 isn't declared in the outer block (or anywhere), so _1 doesn't necessarily need to behave like a local variable declared in the outer block. You could also say "_1 should behave like a normal local variable declared in the inner block", and it seems as plausible as what you said.
The other problem is how to interpret it. I think Ruby master's it behavior is good. I have experienced mistakes of a name conflict issue of a local variable in a different scope, but not as often. However, just adding a read from it outside of a block only changes the meaning of inner it, which I think would increase the frequency of mistakes very much.
When you have nested loops, you would use the iterator of the inner-most block most often. The current behavior of it seems to have more use cases than the one of _1. Ruby has prioritized solving real-world use cases over just making existing features consistent, so I don't think it needs to be consistent with _1 here.
I think it's OK for it to always use the innermost block, but it should be a conscious choice and ideally documented (maybe even part of NEWS).
If we go there, I do think we should use the same semantics for _1, _2, etc for consistency, because they are extremely similar constructs. Otherwise it would become more powerful than _1 but they should really just be equivalent.
Let me get this straight. _1 was introduced long before Prism was merged. Prism allowing _1 in different levels of nested blocks is just a bug of Prism (https://github.com/ruby/prism/issues/3291), so let's not talk about that here. I updated your issue description to clarify what's actually inconsistent between _1 and it.
As to nested _1 being a SyntaxError and nested it being allowed, the behavior of it looks good as is because it seems useful to be able to nest blocks like files.each { YAML.parse_file(it).each { p it } } and it doesn't seem confusing to me. Since this particular case was not discussed before, I'll put this ticket into the dev-meeting agenda to confirm that we all intend that.