Использование do block и фигурных скобок {}

112

Новичок в Ruby, наденьте перчатки для новичков.

Есть ли разница (неясная или практическая) между следующими двумя фрагментами?

my_array = [:uno, :dos, :tres]
my_array.each { |item| 
    puts item
}

my_array = [:uno, :dos, :tres]
my_array.each do |item| 
    puts item
end

Я понимаю, что синтаксис скобок позволит вам разместить блок в одной строке

my_array.each { |item| puts item }

но есть ли какие-либо веские причины использовать один синтаксис вместо другого?

Алан Сторм
источник
5
Отличный вопрос. Еще меня интересует, что предпочитают опытные рубисты.
Джонатан Стерлинг,
3
Дубликат: stackoverflow.com/questions/533008
Майк Вудхаус,
2
возможный дубликат stackoverflow.com/questions/533008/…
Джон Топли,
1
Вы также можете написать однострочник блока do, хотя он действительно полезен только при выполнении чего-то вроде eval ("my_array.each do | item |; put item; end"), но он работает в irb или pry без eval и кавычек. Вы может возникнуть ситуация, когда это предпочтительнее. Но не спрашивайте меня, когда. Это еще одна тема для исследования.
Дуглас Г. Аллен

Ответы:

101

В кулинарной книге Ruby говорится, что синтаксис скобок имеет более высокий приоритет, чемdo..end

Имейте в виду, что синтаксис скобок имеет более высокий приоритет, чем синтаксис do..end. Рассмотрим следующие два фрагмента кода:

1.upto 3 do |x|
  puts x
end

1.upto 3 { |x| puts x }
# SyntaxError: compile error

Второй пример работает только при использовании скобок, 1.upto(3) { |x| puts x }

ТЫ
источник
8
Ах, понял. Итак, из-за порядка приоритета, когда вы используете do, вы передаете блок в качестве дополнительного параметра, но когда вы используете скобки, вы передаете блок как первый параметр результатов вызова (ов) метода в левый.
Алан Сторм
2
Я часто предпочитаю короткие ответы, но ответ bkdir намного яснее.
yakout
71

Это немного старый вопрос, но я хотел бы попытаться объяснить немного больше {}иdo .. end

как было сказано ранее

синтаксис скобок имеет более высокий приоритет, чем do..end

но как это имеет значение:

method1 method2 do
  puts "hi"
end

в этом случае do..endметод method1 будет вызван с блоком, а метод method2 будет передан методу1 в качестве аргумента! что эквивалентноmethod1(method2){ puts "hi" }

но если вы скажете

method1 method2{
  puts "hi"
}

тогда метод2 будет вызван с блоком, затем возвращенное значение будет передано методу1 в качестве аргумента. Что эквивалентноmethod1(method2 do puts "hi" end)

def method1(var)
    puts "inside method1"
    puts "method1 arg = #{var}"
    if block_given?
        puts "Block passed to method1"
        yield "method1 block is running"
    else
        puts "No block passed to method1"
    end
end

def method2
    puts"inside method2"
    if block_given?
        puts "Block passed to method2"
        return yield("method2 block is running")
    else
        puts "no block passed to method2"
        return "method2 returned without block"
    end
end

#### test ####

method1 method2 do 
    |x| puts x
end

method1 method2{ 
    |x| puts x
}

#### вывод ####

#inside method2
#no block passed to method2
#inside method1
#method1 arg = method2 returned without block
#Block passed to method1
#method1 block is running

#inside method2
#Block passed to method2
#method2 block is running
#inside method1
#method1 arg = 
#No block passed to method1
bkdir
источник
39

Обычно это соглашение используется, {}когда вы выполняете небольшую операцию, например, вызов метода, сравнение и т. Д., Так что это имеет смысл:

some_collection.each { |element| puts element }

Но если у вас немного сложная логика, которая состоит из нескольких строк, используйте do .. endпримерно так:

1.upto(10) do |x|
  add_some_num = x + rand(10)
  puts '*' * add_some_num
end

По сути, это сводится к тому, что если логика вашего блока состоит из нескольких строк и не может быть размещена в одной строке, тогда используйте, do .. endа если ваша логика блока проста и всего лишь простая / одна строка кода, тогда используйте {}.

НАН Украины
источник
6
Я согласен с использованием do / end для многострочных блоков, но я буду использовать фигурные скобки, если я привяжу дополнительные методы к концу блока. Стилистически мне больше нравится {...}. Method (). Method (), чем do ... end.method (). Method, но это может быть только я.
Железный Человек
1
Согласен, хотя я предпочитаю назначать результат метода с блоком значимой переменной, а затем вызывать для нее другой метод, как result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodэто просто делает его немного более понятным. Но в целом я бы избегал крушения поезда.
Nas
Интересно, почему этот стиль так популярен. Есть ли причина для этого, кроме «Мы всегда так поступали»?
iGEL
9

В Ruby есть два общих стиля для выбора do endи { }для блоков:

Первый и очень распространенный стиль был популяризирован Ruby on Rails и основан на простом правиле однострочного и многострочного:

  • Используйте фигурные скобки { }для однострочных блоков
  • Используйте do endдля многострочных блоков

Это имеет смысл, потому что do / end плохо читается в однострочном блоке, но для многострочных блоков оставление закрытия }на отдельной строке несовместимо со всем остальным, что используется endв ruby, например определениями модулей, классов и методов (и defт. .) и управляющие структуры ( if, while,case и т.д.)

Второй, менее часто встречающийся стиль известен как семантический, или « скобки Вейриха », предложенный покойным великим рубистом Джимом Вейрихом:

  • Использование do endдля процедурных блоков
  • Используйте скобы { }для функциональных блоков

Это означает, что, когда блок оценивается на предмет его возвращаемого значения , он должен быть цепочкой, а {}фигурные скобки имеют больше смысла для цепочки методов.

С другой стороны, когда блок оценивается на предмет его побочных эффектов , то возвращаемое значение не имеет значения, и блок просто «делает» что-то, поэтому нет смысла связывать его в цепочку.

Это различие в синтаксисе передает визуальное значение оценки блока и того, следует ли вам заботиться о его возвращаемом значении.

Например, здесь возвращаемое значение блока применяется к каждому элементу:

items.map { |i| i.upcase }

Однако здесь не используется возвращаемое значение блока. Это процедурно работает, и делает побочный эффект с ним:

items.each do |item|
  puts item
end

Еще одно преимущество семантического стиля заключается в том, что вам не нужно менять фигурные скобки для завершения / завершения только потому, что в блок была добавлена ​​строка.

Как наблюдение, совпадающие функциональные блоки часто являются однострочными, а процедурные блоки (например, config) - многострочными. Итак, следование стилю Weirich в конечном итоге выглядит почти так же, как и стиль Rails.

Эндрю Вит
источник
1

Я использовал стиль Вейриха в течение многих лет, но просто отошел от него, чтобы всегда использовать подтяжки . Я не помню, чтобы когда-либо использовал информацию из стиля блока, и определение довольно расплывчатое. Например:

date = Timecop.freeze(1.year.ago) { format_date(Time.now) }
customer = Timecop.freeze(1.year.ago) { create(:customer) }

Они прокурорские или функциональные?

А подсчет строк, на мой взгляд, просто бесполезен. Я знаю, есть ли одна или несколько строк, и почему я должен менять стиль только потому, что я добавил или удалил строки?

iGEL
источник