Bug #20955
closedSubtle differences with Proc#parameters for anonymous parameters
Description
p proc { |x| }.parameters #=> [[:opt, :x]]
p lambda { |x| }.parameters #=> [[:req, :x]]
p proc { _1 }.parameters #=> [[:opt, :_1]]
p lambda { _1 }.parameters #=> [[:req, :_1]]
p proc { it }.parameters #=> [[:opt, nil]]
p lambda { it }.parameters #=> [[:req]]
Note the last pair; here are two small confusing problems:
- For proc, unlike numbered parameters, the parameter name is
nil
(notit
). This, though, can be justified to distinguish fromproc { |it| }
case - But also,
proc
has thisnil
for a parameter name, whilelambda
has not.
I am not sure what the “most logical” thing to do here, but I believe that at least proc { it }
and lambda { it }
should be made consistent with each other.
Updated by bkuhlmann (Brooke Kuhlmann) about 1 month ago
Good catch. I would expect the following behavor:
proc { it }.parameters #=> [[:opt, :it]]
lambda { it }.parameters #=> [[:req, :it]]
This would be consistent with existing behavior as shown with the x
and _1
variables. Is there a reason why it
would need to behave differently?
Updated by zverok (Victor Shepelev) about 1 month ago
@bkuhlmann The only possible reason is not to confuse with this:
proc { |it| }.parameters # the parameter is literally named "it"
I am not sure it matters much, but maybe in somebody’s metaprogramming... I don’t remember seeing libraries in Ruby that changed behavior depending on parameter names (and changing it on parameter name it
seems even less plausible), but theoretically, it could happen.
(The situation is unlike _1
, which is NOT allowed to be an explicit parameter name.)
Updated by bkuhlmann (Brooke Kuhlmann) about 1 month ago
@zverok (Victor Shepelev) Makes sense...but this behavior would be nice (again, thinking in terms of consistency):
proc { |_1| }.parameters # _1 is reserved for numbered parameter (SyntaxError)
proc { |it| }.parameters # it is a reserved block parameter (SyntaxError)
I realize this would break backwards compatibility in terms of semantic versioning, though.
That said, I can provide an example of it
-- as currently implemented in Ruby 3.4.0-rc1 -- that would cause issues with my Marameters gem. ⚠️ Please note that the syntax I'm showing you is for the next major release of the gem once Ruby 3.4.0 is released:
parameters = proc { it }.parameters
signature = Marameters.signature(parameters).to_s # " = nil"
Demo = Module.new do
module_eval <<~METHOD, __FILE__, __LINE__ + 1
def self.test(#{signature}) = "This is a demo."
METHOD
end
Demo.test # syntax errors found (SyntaxError)
# ↑ This is because a method signature of `test( = nil)` is definitely not syntactically valid.
I can definitely fix my Marameters gem to account for this use case but would be nice to ensure Method#parameters
always answers an array than can immediately be used to dynamically build a new method signature that is syntactically correct.
Updated by zverok (Victor Shepelev) about 1 month ago
but this behavior would be nice (again, thinking in terms of consistency):
proc { |_1| }.parameters # _1 is reserved for numbered parameter (SyntaxError)
proc { |it| }.parameters # it is a reserved block parameter (SyntaxError)
I believe that when it
was introduced, it was common understanding that it should be as non-invasive as humanly possible, so it should never be deprecated as a local variable name or explicit parameter name. It is a common short word, so amount of codebases that use it, including as an explicit name, is probably significant (as a shortcut for it
em, or "i
ndex of t
ime point" or somesuch).
it
and _1
are explicitly not, and would not be, consistent by this account and by several others (say, allowing usage in the nested blocks — though the latter seems less justified to me).
Updated by bkuhlmann (Brooke Kuhlmann) about 1 month ago
Thanks. When I mentioned "consistency" I was mostly concerned about getting a parameters array from Proc#parameters
that I could use to construct an equivalent method signature but this won't be a problem with normal method signatures (well, sort of), only procs/lambdas will exhibit this behavior. I guess this is OK but does feel weird especially since you can end up with a required/optional parameter with no name. The only non-proc situation I can think of is when using a required parameter with array destructuring. Example:
def demo((one, two)) = puts "One: #{one}, Two: #{two}"
method(:demo).parameters # [[:req]]
Updated by k0kubun (Takashi Kokubun) about 1 month ago
- Related to Feature #18980: `it` as a default block parameter added
Updated by k0kubun (Takashi Kokubun) about 1 month ago
Agreed that it
should work like _1
here. You couldn't distinguish proc {it}.parameters
with proc {|it|}.parameters
, but these procs work in the same way anyway, so it shouldn't matter.
Note that nobu's PR https://github.com/ruby/ruby/pull/12398 for https://bugs.ruby-lang.org/issues/20965 fixes this too.
Updated by nobu (Nobuyoshi Nakada) about 1 month ago
- Status changed from Open to Closed
Applied in changeset git|46fec0f62a1803d44edb8b06e39ac0f358e56670.
[Bug #20965] Define it
like an ordinary argument (#12398)
Also fixes [Bug #20955]
Updated by alanwu (Alan Wu) 28 days ago
- Related to Bug #20974: Required and optional anonymous parameter show differently in Proc#parameters added
Updated by k0kubun (Takashi Kokubun) 26 days ago · Edited
- Status changed from Closed to Open
Since fixing this raised issues like https://bugs.ruby-lang.org/issues/20970 and it's too close to the release to design the behavior properly, we reverted https://github.com/ruby/ruby/pull/12398 for Ruby 3.4.
I believe that at least proc { it } and lambda { it } should be made consistent with each other.
By the way, they might be already "consistent" with each other. They behave like an anonymous parameter https://bugs.ruby-lang.org/issues/20974.
Updated by alanwu (Alan Wu) 4 days ago
- Status changed from Open to Closed
AFAICT #20974 fully addressed the issue in the OP, and it
behaves the same as anonymous destructuring parameters.
$ ./miniruby -ve 'p proc { it }.parameters #=> [[:opt, nil]]
p lambda { it }.parameters #=> [[:req]]'
ruby 3.5.0dev (2025-01-14T16:46:11Z master b076e9b7ac) +PRISM [arm64-darwin24]
[[:opt]]
[[:req]]