Синтаксис для блочных локальных переменных

Я смущен хорошим стилем для определения локальных переменных блока. Возможные варианты:

Выбор А:

method_that_calls_block { |v, w| puts v, w }

Выбор Б:

method_that_calls_block { |v; w| puts v, w }

Путаница усугубляется, когда я хочу, чтобы локальный блок имел значение по умолчанию. Варианты, которые меня смущают, следующие:

Выбор С:

method_that_calls_block { |v, w = 1| puts v, w }

Выбор Д:

method_that_calls_block { |v, w: 1| puts v, w }

Существует ли соглашение о том, как должны определяться локальные переменные блока?

P.S. Также кажется, что синтаксис ; не работает, когда мне нужно присвоить значение по умолчанию локальной переменной блока! Странный.


person oddjobsman    schedule 05.06.2014    source источник
comment
Нет смысла использовать синтаксис ; для назначения значений по умолчанию, переменные после ; не являются параметрами блока, вы просто объявляете, что блок будет их использовать, и Ruby не должен перезаписывать существующие локальные переменные с помощью то же имя, когда вы делаете.   -  person matt    schedule 05.06.2014
comment
@matt Мне было интересно, можно ли также инициализировать блочные локальные переменные. Как оказалось, это запрещено. Вы также правы в использовании синтаксиса ; block-local только в том случае, если я хочу скрыть переменную с тем же именем во внешней области.   -  person oddjobsman    schedule 05.06.2014
comment
Существует значение по умолчанию, поэтому, если блок вызывается без значения для этого параметра, он будет использовать значение по умолчанию. Поскольку вы никогда не предоставляете значение для локальных переменных блока, наличие значения по умолчанию не имеет смысла. Вы можете просто инициализировать их любыми значениями в самом блоке, зная, что вы не будете затирать существующие переменные.   -  person matt    schedule 05.06.2014
comment
@matt, я тоже предполагал, что это так, но затем запустил (v.2.1) это, что, как я думал, может быть потенциально полезным: [[1,2], [3]].map { |f,s=4| [f,s] } => [[1, 2], [3, 4]]. Я неправильно понял, что вы говорите?   -  person Cary Swoveland    schedule 05.06.2014
comment
Также обратите внимание, что устранение неоднозначности и использование заполнителей для блочных переменных — это методы, которые часто можно использовать для улучшения читабельности. Предположим, например, что вы передаете массив a = [a0, [a1, a2], a3] в блок, но будете использовать только элемент a2. Устранение неоднозначности позволяет вам записать это как |a0,(a1,a2),a3|, а использование заполнителей позволяет упростить его до |_,(_,a2,_)|, которое можно сократить до |_,(_,a2)|. Точно так же устранение неоднозначности позволяет вам писать hash.each_with_object([]) { |(k,v),arr|...}. - Кэри Суовленд 41 минуту назад   -  person Cary Swoveland    schedule 05.06.2014
comment
Предоставление значения по умолчанию блоку parameter (как в |f,s=4| [f,s]) допустимо (и полезно). Предоставление значения по умолчанию для local block не очень полезно (например, |f;s=4| ...) и выдаст ошибку. Использование ; — это не просто альтернатива использованию , — все, что стоит перед точкой с запятой, является параметром, а все, что после — локальной переменной блока.   -  person matt    schedule 05.06.2014
comment
Спасибо за разъяснения, Мэтт.   -  person Cary Swoveland    schedule 05.06.2014


Ответы (2)


Выбор B недействителен. Как указал @matt, это допустимый (хотя и неясный) синтаксис (см. Здесь: Как написать встроенный блок, содержащий область видимости локальной переменной в Ruby?)

Вариант C дает значение по умолчанию для w, которое является обычным значением, а вариант D представляет собой синтаксис для значения по умолчанию аргумент ключевого слова.

person Uri Agassi    schedule 05.06.2014
comment
Итак, вы рекомендуете выбирать C и D по сравнению с другими? - person oddjobsman; 05.06.2014
comment
A, C и D предназначены для разных вариантов использования: A, когда вы передаете два значения, C, когда вы передаете одно или два значения, и D, когда вы передаете значение и хэш... - person Uri Agassi; 05.06.2014
comment
Спасибо за пояснение! - person oddjobsman; 05.06.2014
comment
«Вариант Б недействителен». – это синтаксис, используемый для объявления переменной локальной для блока, чтобы вы не перезаписывали существующую локальную переменную: stackoverflow.com/q/ 22789497/214790. Хотя это немного неясно. - person matt; 05.06.2014

Все четыре из них допустимы, но все они имеют разную семантику — что правильно, зависит от того, чего вы пытаетесь достичь.

Примеры

Рассмотрим следующий метод, который дает несколько значений.

def frob
  yield 1, 2, 3
end

Вариант A: параметры блока

Дайте мне первые два полученных значения, если они есть, остальные меня не интересуют.

frob { |v, w| [v, w].inspect}
# => "[1, 2]"

Вариант B: параметр блока + локальная переменная блока

Дайте мне первое значение, остальные меня не интересуют; и дайте мне дополнительную неинициализированную переменную.

frob { |v; w| [v, w].inspect}
# => "[1, nil]"

Вариант C: параметры блока, некоторые со значениями по умолчанию

Получите первые два значения, и если второе значение не инициализировано, установите для этой переменной значение 1:

frob { |v, w = 1| [v, w].inspect }
# => "[1, 2]" <-- all values are present, default value ignored

Получите первые пять значений, и если пятое значение не инициализировано, установите для этой переменной значение 99:

frob { |v, w, x, y, z = 99| [v, w, x, y, z].inspect }
# => "[1, 2, 3, nil, 99]"

Вариант D: параметры позиционирования и блока ключевых слов

Получите мне первое значение, и если метод выдает параметр ключевого слова w, получите и его; если нет, установите его на 1.

frob { |v, w: 1| [v, w].inspect }
# => "[1, 1]"

Это разработано для случая, когда метод выдает параметры блока:

def frobble
  yield 1, 2, 3, w: 4
end
frobble { |v, w: 1| [v, w].inspect }
# => "[1, 4]"

В Ruby ‹ 2.7 блок с параметром ключевого слова также деструктурирует хеш, хотя Ruby 2.7 даст вам предупреждение об устаревании, как если бы вы передали хэш методу, который принимает аргументы ключевого слова:

def frobnitz
  h = {w: 99}
  yield 1, 2, 3, h
end

# Ruby 2.7
frobnitz { |v, w: 1| [v, w].inspect }
# warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call
# => "[1, 99]"

Ruby 3.0 не выдает предупреждения об устаревании, но также игнорирует хэш:

# Ruby 3.0
frobnitz { |v, w: 1| [v, w].inspect }
# => [1, 1]

Однако предоставление явного аргумента ключевого слова по-прежнему работает, как и ожидалось, в версии 3.0:

# Ruby 3.0
frobble { |v, w: 1| [v, w].inspect }
# => "[1, 4]"

Обратите внимание, что форма аргумента ключевого слова завершится ошибкой, если метод выдаст неожиданные ключевые слова:

def frobnicate
  yield 1, 2, 3, w: 99, z: -99
end
frobnicate { |v, w: 1| [v, w].inspect }
# => ArgumentError (unknown keyword: :z)

Деструктуризация массива

Различия становятся очевидными еще при рассмотрении метода, возвращающего массив:

def gork
  yield [1, 2, 3]
end

Передача блока с одним аргументом даст вам весь массив:

gork { |v| v.inspect }
# => "[1, 2, 3]"

Однако передача блока с несколькими аргументами даст вам элементы массива, даже если вы передадите слишком мало или слишком много аргументов:

gork { |v, w| [v, w].inspect }
# "[1, 2]" 
gork { |v, w, x, y| [v, w, x, y].inspect }
# => "[1, 2, 3, nil]"

Здесь снова может пригодиться синтаксис ; для блочных переменных:

gork { |v; w| [v, w].inspect }
# => "[[1, 2, 3], nil]"

Однако обратите внимание, что даже аргумент ключевого слова все равно приведет к деструктуризации массива:

gork { |v, w: 99| [v, w].inspect }
# => "[1, 99]"
gork { |v, w: 99; x| [v, w, x].inspect }
# => "[1, 99, nil]"

Затенение внешней переменной

Обычно, если вы используете имя внешней переменной внутри блока, вы используете эту переменную:

w = 1; frob { |v| w = 99}; w
# => 99

Вы можете избежать этого с помощью любого из вариантов выше; любой из них будет затенять внешнюю переменную, скрывая внешнюю переменную от блока и гарантируя, что любые эффекты, которые блок оказывает на нее, являются локальными.

Вариант A: параметры блока:

w = 1; frob { |v, w| puts [v, w].inspect; w = 99}; w
# [1, 2]
# => 1

Вариант B: параметр блока + локальная переменная блока

w = 1; frob { |v; w| puts [v, w].inspect; w = 99}; w
# [1, nil]
# => 1

Вариант C: параметры блока, некоторые со значениями по умолчанию

w = 1; frob { |v, w = 33| puts [v, w].inspect; w = 99}; w
# [1, 2]
# => 1

Вариант D: параметры позиционирования и блока ключевых слов

w = 1; frob { |v, w: 33| puts [v, w].inspect; w = 99}; w
# [1, 33]
# => 1

Однако другие поведенческие различия все еще сохраняются.

Значения по умолчанию

Вы не можете установить значение по умолчанию для локальных переменных блока.

frob { |v; w = 1| [v, w].inspect }
# syntax error, unexpected '=', expecting '|'

Вы также не можете использовать аргумент ключевого слова в качестве параметра блока.

frob { |v; w: 1| [v, w].inspect }
# syntax error, unexpected ':', expecting '|'

Если вы знаете, что вызываемый вами метод не возвращает параметр блока, вы можете объявить поддельный параметр блока со значением по умолчанию и использовать его для получения предварительно инициализированного блока. локальная переменная. Повторяется из первого примера выбора D выше:

frob { |v, w: 1| [v, w].inspect }
# => "[1, 1]"
person David Moles    schedule 28.01.2021