块变量的括号规则

Rules on Parenthesis for Block Variables

我 运行 在阅读 The Ruby Way:

时跨越了以下代码段
class Array
  def invert
    each_with_object({}).with_index { |(elem, hash), index| hash[elem] = index }
  end
end

我想确保我理解括号在 (elem, hash) 中的作用。

第一种方法 (each_with_object({})) 将向块生成两个对象。第一个对象将是数组中的元素;第二个对象将是散列。括号确保将这两个对象分配给不同的块变量。如果我改为使用 { |elem, index} #code },则 elem 将是一个由元素和散列组成的数组。我认为这很清楚。

我的困惑在于,如果我不链接这两个方法,我就不必使用括号,而是可以使用:each_with_object({}) { |elem, obj #code }

关于何时在块变量中需要括号的规则是什么?为什么它们在此处的两个示例之间有所不同?我的 简单化 解释是,当方法没有链接时,yield 代码看起来像 yield (elem, obj),但是当方法被链接时,代码看起来像 yield([elem, obj], index). (我们可以推测,如果我们链接第三个方法,则会传入第二个数组)。这个对吗?从最后一个链接方法传入的对象不是数组吗?

我想问题不是所有这些猜想,而是归结为:“当链接接受块的方法时,yield 语句是什么样的?

规则很基本:每个普查员都有一个“签名”。例如。它产生两个参数,那么要传递的过程应该期望接收两个参数:

[1,2,3].each_with_index { |o, i| ...}

当对象可能被展开时,比如hash item,可以使用括号展开。假设迭代器产生一个数组,[*arr]-like 操作是允许的。

以下示例可能会对此有所启发:

[1,2,3].each_with_object('first') # yielding |e,        obj|
       .with_index                # yielding |elem,     idx|
    #  but wait! elem might be expanded here  ⇑⇑⇑⇑
    #                                        |(e, obj), idx|
       .each_with_object('second') do |((elem, fst_obj), snd_idx), trd_obj| 
    puts "e: #{elem}, 1o: #{fst_obj}, 2i: #{snd_idx}, 3o: #{trd_obj}"
end

#⇒ e: 1, 1o: first, 2i: 0, 3o: second
#⇒ e: 2, 1o: first, 2i: 1, 3o: second
#⇒ e: 3, 1o: first, 2i: 2, 3o: second

您的问题仅与块和块变量无关。相反,它涉及 "disambiguating" 数组的规则。

让我们考虑一下你的例子:

[1,2,3].each_with_object({}).with_index {|(elem, hash), index| hash[elem] = index}

我们有:

enum0 = [1,2,3].each_with_object({})
  #=> #<Enumerator: [1, 2, 3]:each_with_object({})> 

我们可以通过将其转换为数组来查看此枚举器的元素:

enum0.to_a
  #=> [[1, {}], [2, {}], [3, {}]]

我们接下来有:

enum1 = enum0.with_index
  #=> #<Enumerator: #<Enumerator: [1, 2, 3]:each_with_object({})>:with_index> 
enum1.to_a
  #=> [[[1, {}], 0], [[2, {}], 1], [[3, {}], 2]] 

您可能想将 enum1 视为 "compound enumerator",但它只是一个枚举数。

你看到 enum1 有三个元素。这些元素通过 Enumerator#each 传递给块。第一个是:

enum1.first
  #=> [[1, {}], 0] 

如果我们只有一个块变量,比如 a,那么

a #=> [[1, {}], 0]

我们可以使用 "disambiguation" 以不同的方式分解它。例如,我们可以这样写:

a,b = [[1, {}], 0]
a #=> [1, {}] 
b #=> 0 

现在让我们找出所有元素:

a,b,c = [[1, {}], 0]
a #=> [1, {}] 
b #=> 0 
c #=> nil 

糟糕!那不是我们想要的。我们刚刚经历了 "disambiguate" 中的 "ambiguous"。我们需要这样写,以便我们的意图明确。我们通过添加括号来做到这一点。通过这样做,您在告诉 Ruby、"decompose the array in this position to its constituent elements"。我们有:

(a,b),c = [[1, {}], 0]
a #=> 1 
b #=> {} 
c #=> 0 

消除歧义非常有用。例如,假设一个方法返回数组:

[[1,[2,3],[[4,5],{a: 6}]],7]

并且我们希望提取所有的单个值。我们可以这样做:

(a,(b,c),((d,e),f)),g = [[1,[2,3],[[4,5],{a: 6}]],7]
a #=> 1 
b #=> 2 
c #=> 3 
d #=> 4 
e #=> 5 
f #=> {:a=>6} 
g #=> 7 

同样,您只需要记住括号只是表示 "decompose the array in this position to its constituent elements"。