Как Ruby Array #count обрабатывает множественные аргументы блока - PullRequest
0 голосов
/ 07 января 2019

Когда я выполняю следующее:

[[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

Ответы [ 2 ]

0 голосов
/ 07 января 2019

Я посмотрел документацию для 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 за итерацию.

0 голосов
/ 07 января 2019

Так же, как и у заданий, есть (не очень) секретный ярлык. Если правая сторона является массивом, а левая сторона имеет несколько переменных, массив разделяется, поэтому следующие две строки идентичны:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...