Використання do block vs braces {}


112

Новачок у рубіні, надіньте рукавички для новачків.

Чи є різниця (незрозуміла чи практична) між цими наступними фрагментами?

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
Чудове запитання. Мене також цікавить, що воліють досвідчені рубіністи.
Джонатан Стерлінг


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 набагато чіткіша.
якут

71

Це трохи старе питання, але я хотів би спробувати пояснити трохи більше про {}таdo .. end

як сказано раніше

синтаксис дужок має вищий порядок пріоритетності, ніж do..end

але як це має значення:

method1 method2 do
  puts "hi"
end

у цьому випадку метод1 буде викликаний з блоком, do..endа метод2 буде переданий до методу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

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 () over do ... end.method (). Method, але це я можу просто я.
Бляшаний чоловік

1
Погоджено, хоча я віддаю перевагу призначити результат методу з блоком значущій змінній, а потім називати інший метод на ньому, як result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodтакий, що просто робить його трохи легше зрозумілим. Але взагалі я б уникнув аварії поїзда.
нас

Цікаво, чому цей стиль такий популярний. Чи є якась причина для цього, крім "Ми завжди робили це так"?
iGEL

9

У Ruby є два загальні стилі вибору do endпроти { }блоків:

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

  • Використовуйте дужки { }для однорядкових блоків
  • Використовувати do endдля багаторядкових блоків

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

Другий, менш часто зустрічається стиль, відомий як семантичний, або " брекети Вейріха ", запропонований покійним, великим рубіністом Джимом Вейріхом:

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

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

З іншого боку, коли блок оцінюється за його побічними ефектами , тоді повернене значення не має жодного наслідку, а блок просто щось "робить", тому не має сенсу бути ланцюжком.

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

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

items.map { |i| i.upcase }

Однак тут не використовується повернене значення блоку. Він працює процедурно і виконує з ним побічний ефект:

items.each do |item|
  puts item
end

Ще одна перевага семантичного стилю полягає в тому, що вам не потрібно змінювати дужки, щоб зробити / закінчити лише тому, що до блоку була додана лінія.

Як спостереження, випадково функціональні блоки часто є однолінійними, а процедурні блоки (наприклад, конфігурація) є багаторядковими. Отже, слідування стилю Вейріха в кінцевому підсумку виглядає майже так само, як і стиль Rails.


1

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

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

Це прокудальні чи функціональні?

І річ підрахунку рядків на мою думку просто марна. Я знаю, чи є 1 або більше рядків, і чому саме я повинен змінювати стиль лише тому, що додав або видалив рядки?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.