Używanie do block vs braces {}

112

Nowy w rubinie, załóż rękawiczki dla początkujących.

Czy jest jakaś różnica (niejasna lub praktyczna) między następującymi dwoma fragmentami?

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

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

Zdaję sobie sprawę, że składnia nawiasów klamrowych pozwoliłaby umieścić blok w jednej linii

my_array.each { |item| puts item }

ale poza tym, czy są jakieś istotne powody, by używać jednej składni zamiast drugiej?

Alan Storm
źródło
5
Świetne pytanie. Interesuje mnie również to, co wolą doświadczeni rubiniści.
Jonathan Sterling
3
Duplikat: stackoverflow.com/questions/533008
Mike Woodhouse
2
możliwy duplikat stackoverflow.com/questions/533008/ ...
John Topley
1
Możesz także napisać jedną linijkę bloku do, chociaż jest to naprawdę przydatne tylko wtedy, gdy robisz coś takiego jak eval ("my_array.each do | item |; puts item; end"), ale działa w irb lub pry bez znaków eval i cudzysłowu. może natknąć się na sytuację, w której jest to preferowane. Nie pytaj mnie jednak kiedy. To kolejny temat do zbadania.
Douglas G. Allen

Odpowiedzi:

101

Książka kucharska Ruby mówi, że składnia nawiasów ma wyższy priorytet niżdo..end

Należy pamiętać, że składnia nawiasów ma wyższy priorytet niż składnia do..end. Rozważ następujące dwa fragmenty kodu:

1.upto 3 do |x|
  puts x
end

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

Drugi przykład działa tylko wtedy, gdy używany jest nawias, 1.upto(3) { |x| puts x }

TY
źródło
8
Ach, rozumiem. Tak więc ze względu na kolejność pierwszeństwa, kiedy używasz do, przekazujesz blok jako dodatkowy parametr, ale kiedy używasz nawiasów, przekazujesz blok jako pierwszy parametr wyników wywołania metody (-ów) do lewo.
Alan Storm
2
Często wolę krótkie odpowiedzi, ale odpowiedź bkdir jest dużo jaśniejsza.
yakout
71

To trochę stare pytanie, ale chciałbym spróbować wyjaśnić nieco więcej na temat {}ido .. end

jak zostało powiedziane wcześniej

składnia nawiasów ma wyższy priorytet niż do..end

ale jak to robi różnicę:

method1 method2 do
  puts "hi"
end

w tym przypadku metoda1 zostanie wywołana z blokiem, do..enda metoda2 zostanie przekazana do metody method1 jako argument! co jest równoważne zmethod1(method2){ puts "hi" }

ale jeśli powiesz

method1 method2{
  puts "hi"
}

wtedy metoda2 zostanie wywołana z blokiem, a następnie zwrócona wartość zostanie przekazana do metody method1 jako argument. Co jest równoważnemethod1(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
}

#### wynik ####

#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
źródło
39

Ogólnie rzecz biorąc, konwencja jest używana, {}gdy wykonujesz małą operację, na przykład wywołanie metody lub porównanie itp., Więc ma to sens:

some_collection.each { |element| puts element }

Ale jeśli masz nieco złożoną logikę, która prowadzi do wielu linii, użyj do .. end:

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

Zasadniczo sprowadza się to do tego, że jeśli twoja logika blokowa przechodzi do wielu linii i nie można jej dopasować do tej samej linii, użyj, do .. enda jeśli twoja logika blokowa jest prosta i tylko prosta / pojedyncza linia kodu, użyj {}.

nas
źródło
6
Zgadzam się z używaniem do / end dla bloków wieloliniowych, ale pójdę z nawiasami klamrowymi, jeśli połączę dodatkowe metody na końcu bloku. Stylistycznie podoba mi się metoda {...}. Method (). Method () zamiast metody do ... end.method ()., Ale to może być tylko ja.
Tin Man,
1
Zgoda, chociaż wolę przypisać wynik metody z blokiem do znaczącej zmiennej, a następnie wywołać inną metodę, result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodponieważ dzięki temu jest to trochę łatwiejsze do zrozumienia. Ale generalnie unikałbym katastrofy pociągu.
nas
Zastanawiam się, dlaczego ten styl jest tak popularny. Czy jest jakiś inny powód niż „Zawsze robiliśmy to w ten sposób”?
iGEL,
9

Istnieją dwa popularne style wybierania do endi { }blokowania w Rubim:

Pierwszy i bardzo popularny styl został spopularyzowany przez Ruby on Rails i opiera się na prostej zasadzie pojedynczej i wieloliniowej:

  • Użyj nawiasów klamrowych { }dla bloków jednowierszowych
  • Służy do enddo bloków wieloliniowych

Ma to sens, ponieważ do / end czyta źle w jednowierszowym, ale w przypadku bloków wieloliniowych, pozostawienie zamknięcia }wiszącego w osobnym wierszu jest niespójne ze wszystkim innym, co używa endw ruby, takim jak definicje modułów, klas i metod ( defitp. zwalczania.) i ( if, while, caseetc.)

Drugi, rzadziej spotykany styl znany jest jako semantyczny lub „ Weirich Braces ”, zaproponowany przez nieżyjącego już, wielkiego rubinisty Jima Weiricha:

  • Używaj do enddo bloków proceduralnych
  • Użyj szelek { }do bloków funkcjonalnych

Oznacza to, że gdy blok jest oceniany pod kątem wartości zwracanej , powinien być możliwy do utworzenia łańcucha, a {}nawiasy klamrowe mają większy sens w przypadku łączenia metod.

Z drugiej strony, gdy blok jest oceniany pod kątem skutków ubocznych , zwracana wartość nie ma żadnego znaczenia, a blok po prostu coś „robi”, więc nie ma sensu być łączonym w łańcuch.

To rozróżnienie w składni przekazuje wizualne znaczenie oceny bloku i tego, czy należy dbać o jego zwracaną wartość.

Na przykład tutaj wartość zwracana bloku jest stosowana do każdego elementu:

items.map { |i| i.upcase }

Jednak tutaj nie używa wartości zwracanej przez blok. To działa proceduralnie, a robi to efekt uboczny z nim:

items.each do |item|
  puts item
end

Kolejną zaletą stylu semantycznego jest to, że nie trzeba zmieniać nawiasów klamrowych na wykonanie / zakończenie tylko dlatego, że do bloku dodano wiersz.

Z obserwacji wynika, że ​​przypadkowo bloki funkcjonalne są często jednowierszowe, a bloki proceduralne (np. Config) są wieloliniowe. Więc podążanie za stylem Weiricha wygląda prawie tak samo jak styl Rails.

Andrew Vit
źródło
1

Używałem stylu Weirich przez lata, ale po prostu odszedłem od tego, aby zawsze używać aparatu ortodontycznego . Nie pamiętam, żebym kiedykolwiek używał informacji ze stylu bloku, a definicja jest trochę niejasna. Na przykład:

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

Czy są one prokudowe czy funkcjonalne?

A liczenie linii jest moim zdaniem bezużyteczne. Wiem, czy jest 1 lub więcej linii i dlaczego dokładnie powinienem zmienić styl tylko dlatego, że dodałem lub usunąłem linie?

iGEL
źródło