为什么Hash#使用splat运算符合并返回Hashes数组而不是Hashes数组?

问题描述 投票:4回答:2

TL; DR

我通过反复试验解决了这个问题,但是我对splat运算符和pp方法如何一直给我一个不同于我认为的对象的理解显然存在差距。我想了解这个差距,并找出合并散列数组的更好方法。我也希望能够在未来更有效地调试这类事情。

首先是代码示例和调试步骤。我的半满意解决方案和更详细的问题都在底部。

代码

我正在使用MRI Ruby 2.6.2。鉴于Foo类,我希望Foo #windows返回一个合并的哈希值。这是该类的最小示例:

class Foo
  attr_reader :windows

  def initialize
    @windows = {}
  end

  def pry
    { pry: "stuff pry\r" }
  end 

  def irb
    { irb: "stuff irb\r" }
  end 

  def windows= *hashes
    @windows.merge! *hashes
  end
end

foo = Foo.new
foo.windows = foo.pry, foo.irb

问题(带调试)

但是,尝试分配给foo.windows(或者甚至试图用foo.windows= foo.pry, foo.irb来帮助解析器时不那么模糊)我从REPL中得到一个异常:

TypeError:没有将Array隐式转换为Hash

但是,如果我使用单例方法修改实例来捕获*hashes参数的值,我会看到一个哈希数组,我可以合并得很好。考虑以下:

def foo.windows= *hashes
  pp *hashes
end
foo.windows = foo.pry, foo.irb
#=> [{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]

{}.merge *[{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]
#=> {:pry=>"stuff pry\r", :irb=>"stuff irb\r"}

从#pp获取输出给了我一些按预期工作的东西。然而,当我深入挖掘时,事实证明有些东西正在层叠在Hash的额外嵌套上:

def foo.windows= *hashes
  pp *hashes.inspect
end
foo.windows = foo.pry, foo.irb
"[[{:pry=>\"stuff pry\\r\"}, {:irb=>\"stuff irb\\r\"}]]"

即使返回值没有显示,也有一组额外的方括号导致数组嵌套。我真的不明白他们来自哪里。

什么有用

因此,无论出于何种原因,我必须将数据展开,展平它,然后我就能合并:

def foo.windows= *hashes
  @windows.merge! *hashes.flatten
end

# The method still returns unexpected results here, but...
foo.windows = foo.pry, foo.irb
#=> [{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]

# the value stored in the instance variable is finally correct!
foo.windows
#=> {:pry=>"stuff pry\r", :irb=>"stuff irb\r"}

但为什么?

所以是的,我已经设法解决了这个问题。但是,我的问题是关于为什么合并散列不能按预期工作,以及额外的嵌套层来自何处。我不期望哈希数组数组,而是哈希数组。我的理解是否存在差距,或者这是某种奇怪的边缘情况?

更重要的是,为什么这么难以调试?我希望#pp或#inspect向我展示我真正拥有的对象,而不是在我清楚地拥有包含Hash的数组的数组时向我显示一个Hashes数组作为返回值。

arrays ruby hash merge splat
2个回答
2
投票

你错过的是Ruby parser doesn't allow setter methods with more than one parameter

当您将多个参数传递给setter时,它们会自动放在一个数组上(因为a = 1, 2a = [1, 2]的含义相同):

def foo.a=(values)
  pp values
end

foo.a = 1, 2 # [1, 2]

但是,如果定义了splat参数,因为该数组被认为是单个参数,所以会发生这种情况:

def foo.a=(*values)
  pp values
end

foo.a = 1, 2 # [[1, 2]]

3
投票

首先,*hashes可能意味着两件截然不同的事情:

  • def windows=(*hashes)的意思是“按照出现的顺序将传递给此方法的所有参数存储到数组hashes中。”
  • @windows.merge! *hashes使用hashes的项目作为方法调用merge!的个别参数。

但是,当你有一个赋值方法时,listing several values automatically creates an array:

您可以通过在分配时列出多个值来隐式创建数组:

a = 1, 2, 3
p a # prints [1, 2, 3]

因此foo.windows(foo.pry, foo.irb)

def windows(*hashes)
    pp hashes
    @windows.merge! *hashes
end

将按预期打印[{:pry=>"stuff pry\r"}, {:irb=>"stuff irb\r"}]。但是由于你有一个赋值方法,你应该从你的定义中删除星号。

def windows=(hashes)
  @windows.merge!(*hashes)
end
© www.soinside.com 2019 - 2024. All rights reserved.