Как блок кода в Ruby узнает, какая переменная принадлежит аспекту объекта? - PullRequest
0 голосов
/ 26 июня 2018

Рассмотрим следующее:

(1..10).inject{|memo, n| memo + n}

Вопрос:

Откуда n знает, что предполагается хранить все значения из 1..10? Я запутался, как Ruby может понять, что n может быть автоматически ассоциировано с (1..10) сразу, а memo - это просто memo.

Я знаю, что блоки кода Ruby не совпадают с блоками кода C или Java - блоки кода Ruby работают немного по-другому. Я не понимаю, как переменные, которые находятся между вертикальными трубами '|' будет автоматически назначаться частям объекта. Например:

hash1 = {"a" => 111, "b" => 222}
hash2 = {"b" => 333, "c" => 444}
hash1.merge(hash2) {|key, old, new| old}

Как сделать «| ключ, старый, новый |» автоматически назначать себя таким образом, чтобы, когда я набираю «старый» в блоке кода, он автоматически осознавал, что «старый» ссылается на старшее значение хеша? Я никогда никому не назначал «старый», просто объявил об этом. Может кто-нибудь объяснить, как это работает?

Ответы [ 4 ]

0 голосов
/ 27 июня 2018

Кодовый блок - это просто функция без имени. Как и любая другая функция, она может вызываться несколько раз с разными аргументами. Если у вас есть метод

def add(a, b)
  a + b
end

Как add узнает, что иногда a равно 5, а иногда a равно 7?

Enumerable#inject просто вызывает функцию один раз для каждого элемента, передавая элемент в качестве аргумента.

Это выглядит примерно так:

module Enumerable
  def inject(memo)
    each do |el|
      memo = yield memo, el
    end
    memo
  end
end
0 голосов
/ 26 июня 2018

Параметры для блока определяются методом определения. Определение для reduce/inject перегружено ( docs ) и определено в C, но если вы хотите определить его, вы можете сделать это следующим образом (обратите внимание, это не охватывает все перегруженные случаи для фактическое reduce определение):

module Enumerable
  def my_reduce(memo=nil, &blk)
    # if a starting memo is not given, it defaults to the first element
    # in the list and that element is skipped for iteration
    elements = memo ? self : self[1..-1]
    memo ||= self[0]
    elements.each { |element| memo = blk.call(memo, element) }
    memo
  end
end

Это определение метода определяет, какие значения использовать для memo и element, и вызывает переменную blk (блок, переданный методу) с ними в определенном порядке.

Обратите внимание, что блоки не похожи на обычные методы, потому что они не проверяют количество аргументов. Например: (обратите внимание, в этом примере показано использование yield, который является другим способом передачи параметра блока)

def foo
  yield 1
end

# The b and c variables here will be nil
foo { |a, b, c| [a,b,c].compact.sum } # => 1

Вы также можете использовать деконструкцию для определения переменных во время запуска блока, например, если вы хотите reduce над хэшем, вы можете сделать что-то вроде этого:

# this just copies the hash
{a: 1}.reduce({}) { |memo, (key, val)| memo[key] = val; memo }

Как это работает, вызов reduce для хеша неявно вызывает to_a, который преобразует его в список кортежей (например, {a: 1}.to_a = [[:a, 1]]). reduce передает каждый кортеж как второй аргумент в блок. В месте, где вызывается блок, кортеж деконструируется на отдельные переменные ключа и значения.

0 голосов
/ 26 июня 2018

Просто, чтобы упростить некоторые другие хорошие ответы здесь:

Если вы испытываете трудности с пониманием блоков, простой способ представить их как примитивный и временный метод, который вы создаете и выполняете на месте, а значения между символами канала |memo| - это просто сигнатура аргумента.

За аргументами нет специальной специальной концепции, они просто существуют для метода, который вы вызываете для передачи переменной, как и для вызова любого другого метода с аргументом. Подобно методу, аргументы являются «локальными» переменными в пределах области блока (в этом есть некоторые нюансы в зависимости от синтаксиса, который вы используете для вызова блока, но я отступаю, это другое дело).

Метод, который вы передаете блоку, просто вызывает этот «временный метод» и передает ему аргументы, для которых он предназначен. Точно так же, как при обычном вызове метода, с некоторыми небольшими отличиями, такими как отсутствие «обязательных» аргументов. Если вы не определили какие-либо аргументы для получения, они, к счастью, просто не передадут их вместо того, чтобы поднять ArgumentError. Аналогично, если вы определите слишком много аргументов для блока, который будет получен, они будут просто nil внутри блока, без ошибок, поскольку они не определены.

0 голосов
/ 26 июня 2018

А памятка это просто памятка

что значит "просто памятка"? memo и n принимают любые значения inject проходов. И это реализовано для передачи аккумулятора / памятки в качестве первого аргумента и текущего элемента коллекции в качестве второго аргумента.

Как сделать «| ключ, старый, новый |» автоматически назначать себя

Они не "назначают себя". merge назначает их. Или, скорее, передает эти значения (ключ, старое значение, новое значение) в этом порядке в качестве параметров блока.

Если вы вместо этого напишите

hash1.merge(hash2) {|foo, bar, baz| bar}

Это все равно будет работать точно , как и раньше. Имена параметров ничего не значат [здесь]. Это фактические значения, которые имеют значение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...