【问题标题】:Why is Hash#merge using the splat operator returning an Array of Arrays of Hashes instead of an Array of Hashes?为什么 Hash#merge 使用 splat 运算符返回哈希数组而不是哈希数组?
【发布时间】:2019-08-05 18:40:03
【问题描述】:

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 向我展示我真正拥有的对象,而不是在我清楚地拥有一个包含哈希的数组数组时向我展示一个哈希数组作为返回值。

【问题讨论】:

  • 你使用的是哪个版本的 Ruby?
  • @SRack MRI 2.6.2.
  • = 需要单个值;我怀疑 Ruby 正在将您的参数 h1, h2 转换为单个值。 * splat 将(假定的)可变参数移动到一个数组中——由于它接收单个值,您会看到嵌套。例如,stackoverflow.com/q/9280623/438992
  • pp 没有按照你的想法做,因为你在去喷溅。

标签: arrays ruby hash merge splat


【解决方案1】:

你缺少的是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]]

【讨论】:

  • 这个答案和the one from @idmean 都对我有帮助,但这个答案让我更清楚地注意到我正在(ab)使用setter 方法语法这一事实。一旦我意识到这一点,其他自己造成的问题也消失了。
【解决方案2】:

首先,*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

【讨论】:

  • merge! 的文档显示可变参数 shrug。您正在链接到 ruby​​ 2.0; 2.6 文档不同。
  • @DaveNewton 哦,2.6+ 似乎是这样。我的本地 2.5 不支持。
  • 是的,在 2.6 中添加。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 2014-08-09
  • 1970-01-01
  • 1970-01-01
  • 2021-02-05
  • 2019-10-27
相关资源
最近更新 更多