Project

General

Profile

Actions

Bug #20955

open

Subtle differences with Proc#parameters for anonymous parameters

Added by zverok (Victor Shepelev) 3 days ago. Updated 1 day ago.

Status:
Open
Assignee:
-
Target version:
-
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:

  1. For proc, unlike numbered parameters, the parameter name is nil (not it). This, though, can be justified to distinguish from proc { |it| } case
  2. 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.

Updated by bkuhlmann (Brooke Kuhlmann) 2 days 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) 2 days ago

@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.)

Updated by bkuhlmann (Brooke Kuhlmann) 2 days 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) 2 days 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 item, or "index of time 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) 1 day 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]]
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0