Ruby:为什么传递给 send() 的输入被包裹在哈希中?

Ruby: why are inputs passed to send() being wrapped in a Hash?

我正在使用 Ruby 中的 send() 方法来调用我定义的方法。该方法采用 OpenStruct.

这是一个片段,展示了我如何使用 send:

调用我的方法
my_open_struct = OpenStruct.new(foo: "foo")

result = @my_object.send(
            :my_method_that_takes_an_openstruct,
            name_of_openstruct_param: my_open_struct)

问题是在 my_method_that_takes_an_openstruct 中,OpenStruct 参数被包裹在 Hash 中,导致日志输出如下:

Just before calling send #<OpenStruct foo="foo">
Inside my_method_that_takes_an_openstruct: {:name_of_openstruct_param=>#<OpenStruct foo="foo">}

为什么会发生这种情况,如何防止这种环绕行为?

我假设 my_method 看起来像这样

class Foo
  def my_method(arg)
    puts "#{arg}"
  end
end

如果您来自 Python 背景,您可能认为我们可以将 my_method 称为 foo.my_method(1)foo.my_method(arg: 1)。但这不是它在 Ruby 中的工作方式。在 Ruby 中,参数是 命名的或位置的。为了命名一个参数,我们在它后面放一个冒号。

class Foo
  def my_method(arg:)
    puts "#{arg}"
  end
end

现在我们可以做 Foo.new.my_method(arg: 1)Foo.new.send(:my_method, arg: 1),但是 不正确Foo.new.my_method(1)

您获得哈希的原因是兼容性技巧。在旧版本的 Ruby 中,在我们命名参数之前,惯例是在末尾采用单个散列参数

def foo(a, b, opts)
  ...
end

然后下面的两个调用是等价的(前者是后者的语法糖)

foo(1, 2, foo: "bar", bar: "baz")
foo(1, 2, {foo: "bar", bar: "baz"})

基本上,如果参数列表中出现任何类似名称的参数,它们将被转换为单个散列并作为最终参数传递给函数。

此行为是 Ruby 3.0 中的 deprecated as of Ruby 2.7 and removed。现在正确的约定是采用显式命名参数,Ruby 的最新版本支持双 splat ** 运算符,用于将散列 转换为 命名参数,类似于具有相同名称的 Python 运算符。