Verwenden von do block vs klammern {}


112

Neu bei Ruby, zieh deine Newbie-Handschuhe an.

Gibt es einen Unterschied (dunkel oder praktisch) zwischen den folgenden beiden Ausschnitten?

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

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

Mir ist klar, dass Sie mit der Klammer-Syntax den Block in einer Zeile platzieren können

my_array.each { |item| puts item }

Aber gibt es darüber hinaus zwingende Gründe, eine Syntax über die andere zu verwenden?


5
Gute Frage. Mich interessiert auch, was erfahrene Rubinisten bevorzugen.
Jonathan Sterling




1
Sie können auch einen Einzeiler des do-Blocks schreiben, obwohl dies wirklich nur nützlich ist, wenn Sie etwas wie eval ausführen ("my_array.each do | item |; setzt item; end"), aber es funktioniert in irb oder pry ohne eval und Anführungszeichen kann auf eine Situation stoßen, in der dies bevorzugt wird. Frag mich nicht wann. Das ist ein weiteres Thema, das untersucht werden muss.
Douglas G. Allen

Antworten:


100

Laut Ruby-Kochbuch hat die Klammernsyntax eine höhere Rangfolge alsdo..end

Beachten Sie, dass die Klammernsyntax eine höhere Priorität hat als die do..end-Syntax. Betrachten Sie die folgenden zwei Codeausschnitte:

1.upto 3 do |x|
  puts x
end

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

Das zweite Beispiel funktioniert nur, wenn Klammern verwendet werden. 1.upto(3) { |x| puts x }


7
Ah, verstanden. Aufgrund der Rangfolge übergeben Sie bei Verwendung von do den Block als zusätzlichen Parameter. Wenn Sie jedoch die Klammern verwenden, übergeben Sie den Block als ersten Parameter der Ergebnisse der Methodenaufrufe an die linke.
Alan Storm

2
Ich bevorzuge oft kurze Antworten, aber die Antwort von bkdir ist viel klarer.
Yakout

70

Dies ist eine etwas alte Frage, aber ich möchte versuchen, etwas mehr über {}und zu erklärendo .. end

wie es schon gesagt wird

Die Klammer-Syntax hat eine höhere Rangfolge als do..end

aber wie macht dieser Unterschied:

method1 method2 do
  puts "hi"
end

In diesem Fall wird Methode1 mit dem Block von do..endaufgerufen und Methode2 wird als Argument an Methode1 übergeben! das ist äquivalent zumethod1(method2){ puts "hi" }

aber wenn du sagst

method1 method2{
  puts "hi"
}

dann wird method2 mit dem Block aufgerufen, dann wird der zurückgegebene Wert als Argument an method1 übergeben. Welches ist gleichbedeutend mitmethod1(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
}

#### Ausgabe ####

#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

Im Allgemeinen wird die Konvention verwendet, {}wenn Sie eine kleine Operation ausführen, z. B. einen Methodenaufruf oder einen Vergleich usw., sodass dies durchaus sinnvoll ist:

some_collection.each { |element| puts element }

Wenn Sie jedoch eine etwas komplexe Logik haben, die mehrere Zeilen umfasst, verwenden Sie do .. endFolgendes:

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

Grundsätzlich kommt es darauf an, ob Ihre Blocklogik mehrere Zeilen umfasst und nicht in dieselbe Zeile eingepasst werden kann, do .. endund wenn Ihre Blocklogik einfach ist und nur eine einfache / einzelne Codezeile verwendet wird {}.


6
Ich bin damit einverstanden, do / end für mehrzeilige Blöcke zu verwenden, aber ich werde mit geschweiften Klammern arbeiten, wenn ich zusätzliche Methoden an das Ende des Blocks kette. Stilistisch mag ich {...}. Method (). Method () gegenüber do ... end.method (). Method, aber das könnte nur ich sein.
der Blechmann

1
Einverstanden, obwohl ich es vorziehe, das Ergebnis einer Methode mit Block einer aussagekräftigen Variablen zuzuweisen und dann eine andere Methode darauf aufzurufen, result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodda dies nur ein bisschen verständlicher macht. Aber im Allgemeinen würde ich ein Zugunglück vermeiden.
Nas

Ich frage mich, warum dieser Stil so beliebt ist. Gibt es einen anderen Grund dafür als "Wir haben es immer so gemacht"?
iGEL

8

In Ruby gibt es zwei gängige Stile für die Auswahl im do endVergleich { }zu Blöcken:

Der erste und sehr verbreitete Stil wurde von Ruby on Rails populär gemacht und basiert auf einer einfachen Regel von ein- oder mehrzeilig:

  • Verwenden Sie geschweifte Klammern { }für einzeilige Blöcke
  • Verwendung do endfür mehrzeilige Blöcke

Dies ist sinnvoll, da do / end in einem Einzeiler schlecht gelesen wird. Bei mehrzeiligen Blöcken ist es jedoch nicht konsistent, einen Abschluss }in einer eigenen Zeile hängen zu lassen, was endin Ruby verwendet wird, z. B. Modul-, Klassen- und Methodendefinitionen ( defusw.) .) und Kontrollstrukturen ( if, while, case, etc.)

Der zweite, weniger häufig gesehene Stil ist als semantische oder " Weirich Braces " bekannt, die vom verstorbenen, großen Rubinisten Jim Weirich vorgeschlagen wurde:

  • Verwendung do endfür Verfahrensblöcke
  • Verwenden Sie Klammern { }für Funktionsblöcke

Dies bedeutet, dass der Block, wenn er auf seinen Rückgabewert bewertet wird , verkettbar sein sollte und die {}geschweiften Klammern für die Methodenverkettung sinnvoller sind.

Auf der anderen Seite, wenn der Block für seine ausgewertet wird Nebenwirkungen untersucht wird , hat der Rückgabewert keine Konsequenz, und der Block "tut" nur etwas, so dass es keinen Sinn macht, verkettet zu werden.

Diese Unterscheidung in der Syntax vermittelt eine visuelle Bedeutung für die Auswertung des Blocks und ob Sie sich um seinen Rückgabewert kümmern sollten oder nicht.

Hier wird beispielsweise der Rückgabewert des Blocks auf jedes Element angewendet:

items.map { |i| i.upcase }

Hier wird jedoch nicht der Rückgabewert des Blocks verwendet. Es arbeitet prozedural und dabei einen Nebeneffekt mit sich:

items.each do |item|
  puts item
end

Ein weiterer Vorteil des semantischen Stils besteht darin, dass Sie keine geschweiften Klammern für do / end ändern müssen, nur weil dem Block eine Zeile hinzugefügt wurde.

Als Beobachtung sind zufällig funktionale Blöcke häufig einzeilig , und prozedurale Blöcke (z. B. config) sind mehrzeilig. Wenn Sie also dem Weirich-Stil folgen, sehen Sie fast genauso aus wie dem Rails-Stil.


1

Ich habe jahrelang den Weirich-Stil verwendet, mich aber davon entfernt, um immer Zahnspangen zu verwenden . Ich erinnere mich nicht, jemals die Informationen aus dem Blockstil verwendet zu haben, und die Definition ist irgendwie vage. Beispielsweise:

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

Sind diese prokudual oder funktional?

Und die Sache mit der Zeilenanzahl ist meiner Meinung nach einfach nutzlos. Ich weiß, ob es 1 oder mehr Zeilen gibt und warum genau sollte ich den Stil ändern, nur weil ich Zeilen hinzugefügt oder entfernt habe?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.