Використання 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.