当我执行以下操作时:
[[1,1], [2,2], [3,4]].count {|a,b| a != b} # => 1
将块参数a
,b
分别分配给每个内部数组的第一个和第二个值。我不明白这是如何实现的。
文档中针对Array#count
和Enumerable#count
带有块的唯一示例使用单个块参数:
ary.count {|x| x % 2 == 0} # => 3
答案 0 :(得分:5)
就像任务一样,有一个(不是这样)秘密的快捷方式。如果右侧是一个数组,而左侧有多个变量,则该数组将被展开,因此以下两行相同:
a, b, c = [1, 2, 3]
a, b, c = *[1, 2, 3]
当产生的值是一个数组并且有多个参数时,虽然块不是同一块,但是块具有相同的纹理。因此,当您yield [1, 2, 3]
时,这两个块的作用相同:
do |a, b, c|
...
end
do |(a, b, c)|
...
end
因此,在您的情况下,该值被解构,就像您编写此代码一样:
[[1,1], [2,2], [3,4]].count {|(a,b)| a != b} # => 1
如果要与数组一起传递另一个值,则必须明确指定结构,因为数组的解构不会像我们想要的那样自动进行:
[[1,1], [2,2], [3,4]].each.with_index.count {|e,i| i + 1 == e[1] }
# automatic deconstruction of [[1,1],0]:
# e=[1,1]; i=0
[[1,1], [2,2], [3,4]].each.with_index.count {|(a,b),i| i + 1 == b }
# automatic deconstruction of [[1,1],0], explicit deconstruction of [1,1]:
# a=1; b=1; i=0
[[1,1], [2,2], [3,4]].each.with_index.count {|a,b,i| i + 1 == b }
# automatic deconstruction of [[1,1],0]
# a=[1,1]; b=0; i=nil
# NOT what we want
答案 1 :(得分:4)
我看过Array.count和Enumerable.count的文档,并且给出的带有块的唯一示例使用单个块参数...
Ruby与几乎所有主流编程语言一样,不允许用户代码更改语言的基本语义。换句话说,您不会在Array#count
的文档中找到有关块形式参数绑定语义的任何信息,因为块形式参数绑定语义是由Ruby Language Specification指定的,Array#count
不可能对此进行更改。
我不明白这是如何实现的。
这与Array#count
无关。这只是块形式参数的标准块形式参数绑定语义。
块形式参数的形式参数绑定语义不同于方法形式参数的形式参数绑定语义。特别是,它们在处理形式参数和实际参数之间的不匹配方面更加灵活。
yield
多于一个块实际参数,则该块形式参数将绑定到包含该块实际参数的Array
上。yield
正好一个块实际参数,并且一个实际参数是Array
,则块形式参数将绑定到Array
的各个元素。(这就是您在示例中看到的内容。)yield
块实际参数多于该块具有形式参数的块,则多余的实际参数将被忽略。nil
(就像已定义但统一的局部变量一样)。如果仔细观察,您会发现块形式参数的形式参数绑定语义更接近赋值语义,即,您可以想象一个赋值操作符左侧带有块形式参数的赋值,并且阻止在右侧的实际参数。
如果您具有如下定义的块:
{|a, b, c|}
,并yield
像这样:
yield 1, 2, 3, 4
您几乎可以想象到块形式参数绑定是这样工作的:
a, b, c = 1, 2, 3, 4
如果您的问题中有这种情况,那么您有一个这样定义的块:
{|a, b|}
,并yield
像这样:
yield [1, 2]
您几乎可以想象到块形式参数绑定是这样工作的:
a, b = [1, 2]
如您所知,当然会有以下结果:
a #=> 1
b #=> 2
有趣的事实:在Ruby 1.8之前,使用 actual 分配来阻止形式参数绑定 !例如,您可以将常量,实例变量,类变量,全局变量甚至属性writer(!!!)定义为形式参数,然后yield
进入该块时, Ruby 按字面意思执行任务:
class Foo
def bar=(value)
puts "`#{__method__}` called with `#{value.inspect}`"
@bar = value
end
attr_reader :bar
end
def set_foo
yield 42
end
foo = Foo.new
set_foo {|foo.bar|}
# `bar=` called with `42`
foo.bar
#=> 42
疯了吧?
这些块形式参数绑定语义的最广泛应用是使用Hash#each
(或使用Enumerable
实例作为接收者的任何Hash
方法)时。 Hash#each
方法yield
是一个单一的包含两个元素的Array
,其中包含键和值作为该块的实际参数,但是我们几乎总是将其视为{{1} }将键和值作为单独的实际参数。通常,我们更喜欢写
yield
在
hsh.each do |k, v|
puts "The key is #{k} and the value is #{v}"
end
这完全等同于您在问题中看到的内容。我敢打赌,您从未问过自己,为什么只将hsh.each do |key_value_pair|
k, v = key_value_pair
puts "The key is #{k} and the value is #{v}"
end
传递给一个Hash#each
,为什么可以将带有两个块形式参数的块传递给yield
?嗯,这种情况是完全相同的。您正在将具有两个块形式参数的块传递给方法,该方法Array
每次迭代yield
。