การใช้ 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
คำถามที่ดี ฉันยังสนใจว่านักทับทิมที่มีประสบการณ์ชอบอะไร
Jonathan Sterling


2
เป็นไปได้ที่จะซ้ำกันของstackoverflow.com/questions/533008/…
John Topley


1
นอกจากนี้คุณยังสามารถเขียนหนึ่งซับของ do block ได้แม้ว่ามันจะมีประโยชน์จริงๆก็ต่อเมื่อทำบางอย่างเช่น eval ("my_array.each do | item |; puts item; end") แต่มันใช้งานได้ใน irb หรือ pry โดยไม่ต้อง eval และเครื่องหมายอัญประกาศ อาจเจอสถานการณ์ที่ต้องการ อย่าถามฉันว่าเมื่อไร นั่นเป็นอีกหัวข้อหนึ่งที่ต้องตรวจสอบ
Douglas

คำตอบ:


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 คุณกำลังส่งบล็อกเป็นพารามิเตอร์เพิ่มเติม แต่เมื่อคุณใช้วงเล็บคุณกำลังส่งบล็อกเป็นพารามิเตอร์แรกของผลลัพธ์ของการเรียกใช้เมธอดไปที่ ทางซ้าย.
Alan Storm

2
ฉันมักจะชอบคำตอบสั้น ๆ แต่คำตอบของ bkdir นั้นชัดเจนกว่ามาก
yakout

71

นี่เป็นคำถามเก่าไปหน่อย แต่ฉันอยากจะลองอธิบายเพิ่มเติมเกี่ยวกับ{}และdo .. end

เหมือนที่เคยพูดไว้ก่อนหน้านี้

ไวยากรณ์วงเล็บมีลำดับความสำคัญสูงกว่า do..end

แต่สิ่งนี้สร้างความแตกต่างอย่างไร:

method1 method2 do
  puts "hi"
end

ในกรณีนี้ method1 จะถูกเรียกด้วย block of do..endและ method2 จะถูกส่งไปยัง method1 เป็นอาร์กิวเมนต์! ซึ่งเทียบเท่ากับmethod1(method2){ puts "hi" }

แต่ถ้าคุณพูด

method1 method2{
  puts "hi"
}

จากนั้น method2 จะถูกเรียกด้วย block จากนั้นค่าที่ส่งคืนจะถูกส่งไปยัง method1 เป็นอาร์กิวเมนต์ ซึ่งเทียบเท่ากับ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และถ้าตรรกะบล็อกของคุณเป็นเรื่องง่ายและง่ายเพียงแค่ A / {}บรรทัดเดียวของรหัสแล้วการใช้งาน


6
ฉันเห็นด้วยกับการใช้ do / end สำหรับบล็อกหลายบรรทัด แต่ฉันจะใช้เครื่องหมายวงเล็บถ้าฉันผูกมัดวิธีการเพิ่มเติมไว้ที่ส่วนท้ายของบล็อก ในทางโวหารฉันชอบ {... } method (). method () over do ... end.method (). method แต่นั่นอาจเป็นฉันก็ได้
The Tin Man

1
เห็นด้วยแม้ว่าฉันต้องการกำหนดผลลัพธ์ของวิธีการที่มีบล็อกให้กับตัวแปรที่มีความหมายแล้วเรียกใช้วิธีอื่นในลักษณะนี้result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodเนื่องจากทำให้เข้าใจได้ง่ายขึ้นเล็กน้อย แต่โดยทั่วไปแล้วฉันจะหลีกเลี่ยงอุบัติเหตุรถไฟ
NAS

ฉันสงสัยว่าทำไมสไตล์นี้ถึงได้รับความนิยม มีเหตุผลอื่นนอกจาก "เราทำแบบนี้มาตลอด"?
iGEL

9

มีสองสไตล์ทั่วไปสำหรับการเลือกdo endเทียบกับ{ }บล็อกใน Ruby:

รูปแบบแรกและแบบทั่วไปได้รับความนิยมโดย Ruby on Rails และเป็นไปตามกฎง่ายๆของ single vs. multi-line:

  • ใช้วงเล็บปีกกา{ }สำหรับบล็อกบรรทัดเดียว
  • ใช้do endสำหรับบล็อกหลายบรรทัด

สิ่งนี้สมเหตุสมผลเพราะ do / end อ่านได้ไม่ดีใน one-liner แต่สำหรับบล็อกหลายบรรทัดการปล่อยให้การปิด}ห้อยอยู่บนบรรทัดของตัวเองนั้นไม่สอดคล้องกับสิ่งอื่น ๆ ที่ใช้endในทับทิมเช่นคำจำกัดความของโมดูลคลาสและวิธีการ ( defฯลฯ .) และโครงสร้างการควบคุม ( if, while, caseฯลฯ )

รูปแบบที่สองที่ไม่ค่อยพบเห็นบ่อยนักเรียกว่าความหมายหรือ "วีริชจัดฟัน " ซึ่งเสนอโดยจิมไวริชนักทับทิมผู้ล่วงลับผู้ยิ่งใหญ่:

  • ใช้do endสำหรับบล็อกขั้นตอน
  • ใช้วงเล็บปีกกา{ }สำหรับบล็อกที่ใช้งานได้

ซึ่งหมายความว่าเมื่อบล็อกได้รับการประเมินมูลค่าที่ส่งคืนแล้วควรเป็นแบบ chainable และ{}วงเล็บปีกกามีความหมายมากกว่าสำหรับการผูกมัดวิธีการ

ในทางกลับกันเมื่อบล็อกได้รับการประเมินผลข้างเคียงแล้วมูลค่าที่ส่งคืนจะไม่มีผลใด ๆ และบล็อกเป็นเพียงการ "ทำ" บางสิ่งบางอย่างดังนั้นจึงไม่สมเหตุสมผลที่จะถูกล่ามโซ่

ความแตกต่างในไวยากรณ์นี้บ่งบอกถึงความหมายที่เป็นภาพเกี่ยวกับการประเมินผลของบล็อกและคุณควรสนใจเกี่ยวกับค่าที่ส่งคืนหรือไม่

ตัวอย่างเช่นที่นี่ค่าส่งคืนของบล็อกจะถูกนำไปใช้กับทุกรายการ:

items.map { |i| i.upcase }

อย่างไรก็ตามในที่นี้จะไม่ใช้ค่าส่งคืนของบล็อก มันดำเนินการตามขั้นตอนและทำผลข้างเคียงกับมัน:

items.each do |item|
  puts item
end

ข้อดีอีกอย่างของรูปแบบความหมายคือคุณไม่จำเป็นต้องเปลี่ยนเครื่องหมายวงเล็บเพื่อทำ / สิ้นสุดเพียงเพราะมีการเพิ่มบรรทัดลงในบล็อก

ตามข้อสังเกตบล็อกการทำงานโดยบังเอิญมักเป็นแบบซับเดียวและบล็อกขั้นตอน (เช่น config) เป็นแบบหลายบรรทัด ดังนั้นการทำตามสไตล์ Weirich จะดูเหมือนเกือบจะเหมือนกับสไตล์ Rails


1

ฉันใช้สไตล์ Weirich มาหลายปีแล้ว แต่เพิ่งย้ายออกไปจากนี้เพื่อใช้วงเล็บปีกกาเสมอ ฉันจำไม่ได้ว่าเคยใช้ข้อมูลจากรูปแบบบล็อกและคำจำกัดความนั้นค่อนข้างคลุมเครือ ตัวอย่างเช่น:

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

เหล่านี้เป็น procudual หรือใช้งานได้?

และสิ่งที่นับเส้นก็ไร้ประโยชน์ในความคิดของฉัน ฉันรู้ไม่ว่าจะมี 1 บรรทัดขึ้นไปและทำไมฉันจึงควรเปลี่ยนสไตล์เพียงเพราะฉันเพิ่มหรือลบบรรทัด

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.