Bug #20955
open
Subtle differences with Proc#parameters for anonymous parameters
Added by zverok (Victor Shepelev) 3 days ago.
Updated 1 day ago.
ruby -v:
ruby 3.4.0dev (2024-12-15T13:36:38Z master 366fd9642f) +PRISM [x86_64-linux]
[ruby-core:120252]
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
(not it
). This, though, can be justified to distinguish from proc { |it| }
case
- But also,
proc
has this nil
for a parameter name, while lambda
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.
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?
@bkuhlmann (Brooke Kuhlmann) 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.)
@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.
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).
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]]
Also available in: Atom
PDF
Like0
Like0Like0Like0Like0Like0