Project

General

Profile

Actions

Bug #20930

open

Different semantics for nested `it` and `_1`

Added by Eregon (Benoit Daloze) 14 days ago. Updated 7 days ago.

Status:
Open
Assignee:
-
Target version:
-
ruby -v:
ruby 3.4.0dev (2024-12-04T19:29:24Z master 3c91a1e5fd) +PRISM [x86_64-linux]
[ruby-core:120104]

Description

With --parser=parse.y:

$ 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)

Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #18980: Re-reconsider numbered parameters: `it` as a default block parameterClosedk0kubun (Takashi Kokubun)Actions

Updated by Eregon (Benoit Daloze) 14 days ago

With --parser=parse.y:

$ 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)

Which is still inconsistent between it and _1.

Actions #2

Updated by Eregon (Benoit Daloze) 14 days ago

  • Related to Feature #18980: Re-reconsider numbered parameters: `it` as a default block parameter added

Updated by mame (Yusuke Endoh) 14 days ago · Edited

Good catch. I see two problems.

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:

Prism.parse("[1].each { _1; [2].each { _1 } }", version: "3.3.0")

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.

[1].each {     [2].each { p it }} #=> 2
[1].each { it; [2].each { p it }} #=> 1 (!)

Updated by k0kubun (Takashi Kokubun) 13 days ago

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.

I agree with @mame (Yusuke Endoh) 's opinion.

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.

Updated by Eregon (Benoit Daloze) 12 days ago

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.

Updated by k0kubun (Takashi Kokubun) 9 days ago · Edited

  • Description updated (diff)

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.

Actions #7

Updated by k0kubun (Takashi Kokubun) 9 days ago

  • Description updated (diff)

Updated by mame (Yusuke Endoh) 7 days ago

Discussed at the dev meeting, and matz confirmed that the current master's behavior is good.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like2Like0Like0