Я посмотрел документацию для Array.count и Enumerable.count, и единственный пример, приведенный с блоком, использует один аргумент блока ...
Ruby, как и почти все основные языки программирования, не позволяет пользовательскому коду изменять фундаментальную семантику языка. Другими словами, вы не найдете ничего о семантике связывания формальных параметров блоков в документации Array#count
, потому что семантика связывания формальных параметров блоков определяется спецификацией языка Ruby, и 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 связывание формальных параметров блока было с использованием фактического назначения! Вы можете, например, определить константу, переменную экземпляра, переменную класса, глобальную переменную и даже средство записи атрибутов (!!!) в качестве формального параметра, и когда вы 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
, содержащий ключ и значение в качестве фактического аргумента блока, но мы почти всегда рассматриваем его так, как если бы он 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
s за один Array
? Ну, этот случай точно такой же . Вы передаете блок с двумя блочными формальными параметрами методу, который yield
один Array
за итерацию.