Feature #18980
closed`it` as a default block parameter
Description
Problem¶
Numbered parameters (_1
, _2
, ...) look like unused local variables and I don't feel motivated to use them, even though I need this feature very often and always come up with _1
.
[1, 2, 3].each { puts _1 }
I have barely used it in the last 2~3 years because it looks like a compromised syntax. I even hesitate to use it on IRB.
Why I don't use _1
I'm not clever enough to remember the order of parameters. Therefore, when a block has multiple parameters, I'd always want to name those parameters because which is _1
or _2
is not immediately obvious. Thus I would use this feature only when a block takes a single argument, which is actually pretty common.
If I use _1
, it feels like there might be a second argument, and you might waste time to think about _2
, even if _2
doesn't exist, which is a cognitive overhead. If you use it
, it kinda implies there's only a single argument, so you don't need to spend time remembering whether _2
exists or not. It is important for me that there's no number in it
.
Proposal¶
- Ruby 3.3: Warn
it
method calls without a receiver, arguments, or a block. - Ruby 3.4: Introduce
it
as follows.
Specification¶
[1, 2, 3].each { puts it }
it
s behavior should be as close to _1
as possible. it
should treat array arguments in the same way as _1
. it
doesn't work in a block when an ordinary parameter is defined. it
is implemented as a special case of getlocal
insn, not a method. it
without an argument is considered _1
or a normal local variable if defined. it
is considered a method call only when it has any positional/keyword/block arguments.
Full specification
# Ruby 3.4
def foo
it #=> method call
_1 #=> method call
1.times do
p it #=> 0
it = "foo"
p it #=> "foo"
p _1 #=> 0
_1 = "foo" # Syntax Error
p _1 #=> N/A
p foo #=> method call
foo = 1
p foo #=> local var
it "foo" do # method call (rspec)
end
end
1.times do ||
p _1 # Syntax Error
it # method call
end
1.times do
["Foo"].any? {|| it } # method call
end
yield_1_and_2 do # yield 1, 2
p _1 #=> 1
p it #=> 1
end
yield_ary do # yield [1, 2]
p _1 #=> [1, 2]
p it #=> [1, 2]
end
1.times do
p [_1, it] # Syntax Error
p [_2, it] # Syntax Error
end
end
Past discussions¶
- [Feature #4475] default variable name for parameter: Proposed
it
, and merged as@1
.- 2019/03/13: DevelopersMeeting20190311Japan
- 2019/04/17: DevelopersMeeting20190417Japan
- 2019/04/20: Ruby Committers vs the World
- [Feature #15723] Reconsider numbered parameters: Renamed
@1
to_1
.- 2019/08/29: DevelopersMeeting20190829Japan
- [Feature #15897]
it
as a default block parameter: Proposedit
, and got closed because_1
was merged. - [Feature #18980] Re-reconsider numbered parameters: (this ticket)
- 2022/09/08: Ruby Committers vs the World
Compatibility¶
it
has not necessarily been rejected by Matz; he just said it's difficult to keep compatibility and it
or this
could break existing code. It feels like everybody thinks it
is the most beautiful option but is not sure if it
breaks compatibility. But, in reality, does it
?
The following cases have been discussed:
-
it
method, most famously in RSpec: You almost always pass a positional and/or block argument to RSpec'sit
, so the conflict is avoided with my proposal. You virtually never use a completely nakedit
(comment). -
it
local variable: With the specification in my proposal, the existing code can continue to work if we considerit
as a local variable when defined.
With the specification in my proposal, existing code seems to break if and only if you call a method #it
without an argument. But it seems pretty rare (reminder: a block given to an RSpec test case is also an argument). It almost feels like people are too afraid of compatibility problems that barely exist or have not really thought about options to address them.
Also, you could always experiment with just showing warnings, which doesn't break any compatibility. Even if it takes 2~3 years of a warning period, I'd be happy to use that in 3 years.
Confusion¶
We should separately discuss incompatible cases and "works but confusing" cases. Potential confusion points:
- RSpec's
it "tests something" do ... end
vsit
inside thedo ... end
-
it
could be a local variable or_1
, depending on the situation
My two cents: You'd rarely need to write it
directly under RSpec's it
block, and you would just name a block argument for that case. In a nested block under a test case, I don't think you'd feel it
is RSpec's. When you use a local variable it = 1
, you'd use the local variable in a very small scope or few lines because otherwise, it'd be very hard to figure out what the local variable has anyway. So you'd likely see the assignment it = 1
near the use of the local variable and you could easily notice it
is not _1
. If not, such code would be confusing and fragile even without this feature. The same applies when it
is a method/block argument.
I believe it wouldn't be as confusing as some people think, and you can always choose to not use it
in places where it
is confusing.
Updated by k0kubun (Takashi Kokubun) over 2 years ago
- Is duplicate of Feature #15897: `it` as a default block parameter added
Updated by k0kubun (Takashi Kokubun) over 2 years ago
- Related to Feature #4475: default variable name for parameter added
Updated by k0kubun (Takashi Kokubun) over 2 years ago
- Related to Misc #15723: Reconsider numbered parameters added
Updated by k0kubun (Takashi Kokubun) over 1 year ago
- Description updated (diff)
- Status changed from Open to Assigned
- Assignee set to k0kubun (Takashi Kokubun)
- Target version set to 3.4
Updated by Eregon (Benoit Daloze) 4 months ago
- Related to Bug #20930: Different semantics for nested `it` and `_1` added
Updated by k0kubun (Takashi Kokubun) 3 months ago
- Related to Bug #20965: `it` vs `binding.local_variables` added
Updated by k0kubun (Takashi Kokubun) 3 months ago
- Subject changed from Re-reconsider numbered parameters: `it` as a default block parameter to `it` as a default block parameter
Updated by k0kubun (Takashi Kokubun) 3 months ago
- Related to Bug #20955: Subtle differences with Proc#parameters for anonymous parameters added
Updated by k0kubun (Takashi Kokubun) 3 months ago
- Related to Bug #20970: `it /1/i` raises undefined method 'it' for main (NoMethodError) even if binding.local_variables includes `it` added