Sử dụng do block so với dấu ngoặc nhọn {}


112

Mới sử dụng ruby, hãy đeo găng tay cho người mới của bạn.

Có sự khác biệt nào (khó hiểu hoặc thực tế) giữa hai đoạn trích sau không?

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

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

Tôi nhận ra rằng cú pháp dấu ngoặc nhọn sẽ cho phép bạn đặt khối trên một dòng

my_array.each { |item| puts item }

nhưng bên ngoài đó có bất kỳ lý do thuyết phục nào để sử dụng cú pháp này hơn cú pháp kia không?


5
Câu hỏi tuyệt vời. Tôi cũng quan tâm đến những gì các nhà nghiên cứu ruby ​​có kinh nghiệm thích.
Jonathan Sterling


2
bản sao có thể có của stackoverflow.com/questions/533008/…
John Topley


1
Bạn cũng có thể viết một lớp lót của khối do mặc dù nó chỉ thực sự hữu ích khi làm một cái gì đó như eval ("my_array.each do | item |; put item; end") nhưng nó hoạt động trong irb hoặc pry mà không có đánh giá và dấu ngoặc kép Bạn có thể gặp tình huống khi điều đó được ưu tiên. Đừng hỏi tôi khi nào. Đó là một chủ đề khác cần được điều tra.
Douglas G. Allen

Câu trả lời:


100

Sách dạy nấu ăn Ruby nói rằng cú pháp ngoặc có thứ tự ưu tiên cao hơndo..end

Hãy nhớ rằng cú pháp ngoặc có mức độ ưu tiên cao hơn cú pháp do..end. Hãy xem xét hai đoạn mã sau:

1.upto 3 do |x|
  puts x
end

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

Ví dụ thứ hai chỉ hoạt động khi sử dụng dấu ngoặc đơn, 1.upto(3) { |x| puts x }


7
À, hiểu rồi. Vì vậy, do thứ tự ưu tiên, khi bạn sử dụng do, bạn đang chuyển khối như một tham số bổ sung, nhưng khi bạn sử dụng dấu ngoặc, bạn đang chuyển khối làm tham số đầu tiên của kết quả của (các) phương thức gọi tới Bên trái.
Alan Storm

2
Tôi thường thích câu trả lời ngắn nhưng câu trả lời của bkdir rõ ràng hơn nhiều.
yakout

70

Đây là một câu hỏi hơi cũ nhưng tôi muốn thử giải thích thêm một chút về {}do .. end

như nó được nói trước đây

cú pháp ngoặc có thứ tự ưu tiên cao hơn do..end

nhưng cái này tạo ra sự khác biệt như thế nào:

method1 method2 do
  puts "hi"
end

trong trường hợp này, method1 sẽ được gọi với khối do..endvà method2 sẽ được chuyển cho method1 như một đối số! tương đương vớimethod1(method2){ puts "hi" }

nhưng nếu bạn nói

method1 method2{
  puts "hi"
}

thì method2 sẽ được gọi cùng với khối, sau đó giá trị trả về sẽ được chuyển cho method1 như một đối số. Tương đương vớimethod1(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
}

#### đầu ra ####

#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

Nói chung, quy ước là sử dụng {}khi bạn đang thực hiện một thao tác nhỏ, ví dụ: gọi phương thức hoặc so sánh, v.v. vì vậy điều này hoàn toàn hợp lý:

some_collection.each { |element| puts element }

Nhưng nếu bạn có logic hơi phức tạp đi đến nhiều dòng thì hãy sử dụng do .. endnhư:

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

Về cơ bản, nếu logic khối của bạn có nhiều dòng và không thể được khớp trên cùng một dòng thì hãy sử dụng do .. endvà nếu khối logic của bạn đơn giản và chỉ là một dòng mã / đơn giản thì hãy sử dụng {}.


6
Tôi đồng ý với việc sử dụng do / end cho các khối nhiều dòng, nhưng tôi sẽ sử dụng dấu ngoặc nhọn nếu tôi đang xâu chuỗi các phương thức bổ sung vào cuối khối. Về mặt phong cách, tôi thích {...}. Method (). Method () hơn là do ... end.method (). Method, nhưng đó có thể chỉ là tôi.
the Tin Man

1
Đồng ý, mặc dù tôi thích gán kết quả của một phương thức với khối cho một biến có ý nghĩa và sau đó gọi một phương thức khác trên đó như result_with_some_condition = method{|c| c.do_something || whateever}; result_with_some_condition.another_methodvậy chỉ làm cho nó dễ hiểu hơn một chút. Nhưng nói chung tôi sẽ tránh được một vụ đắm tàu.
nas

Tôi tự hỏi, tại sao phong cách này lại phổ biến đến vậy. Có lý do nào cho nó ngoài "Chúng tôi luôn làm theo cách này"?
iGEL

9

Có hai kiểu phổ biến để chọn do endso { }với các khối trong Ruby:

Kiểu đầu tiên và rất phổ biến đã được phổ biến bởi Ruby on Rails, và dựa trên một quy tắc đơn giản của một dòng so với nhiều dòng:

  • Sử dụng dấu ngoặc nhọn { }cho các khối một dòng
  • Sử dụng do endcho các khối nhiều dòng

Điều này có ý nghĩa bởi vì do / end đọc không tốt trong một lớp lót, nhưng đối với các khối nhiều dòng, việc để một đóng cửa }treo trên dòng riêng của nó không nhất quán với mọi thứ khác sử dụng endtrong ruby, chẳng hạn như định nghĩa mô-đun, lớp và phương thức ( defv.v. cấu trúc điều khiển.) và ( if, while, case, vv)

Thứ hai, phong cách ít thường xuyên nhìn thấy được gọi là ngữ nghĩa, hoặc " Weirich niềng răng ", bởi muộn, rubyist lớn Jim Weirich đề xuất:

  • Sử dụng do endcho các khối thủ tục
  • Sử dụng dấu ngoặc nhọn { }cho các khối chức năng

Điều này có nghĩa là khi khối được đánh giá cho giá trị trả về của nó , nó sẽ có thể phân phối được và các {}dấu ngoặc nhọn có ý nghĩa hơn đối với chuỗi phương thức.

Mặt khác, khi khối được đánh giá về các tác dụng phụ của nó , thì giá trị trả về không có hậu quả gì, và khối chỉ đang "làm" điều gì đó, vì vậy nó không có ý nghĩa gì khi bị xâu chuỗi.

Sự khác biệt này trong cú pháp truyền đạt ý nghĩa trực quan về việc đánh giá khối và liệu bạn có nên quan tâm đến giá trị trả về của nó hay không.

Ví dụ: ở đây giá trị trả về của khối được áp dụng cho mọi mục:

items.map { |i| i.upcase }

Tuy nhiên, ở đây nó không sử dụng giá trị trả về của khối. Nó hoạt động procedurally, và làm một tác dụng phụ với nó:

items.each do |item|
  puts item
end

Một lợi ích khác của kiểu ngữ nghĩa là bạn không cần thay đổi dấu ngoặc nhọn để thực hiện / kết thúc chỉ vì một dòng đã được thêm vào khối.

Theo quan sát, các khối chức năng trùng hợp thường là một lớp lót và các khối thủ tục (ví dụ: cấu hình) là nhiều dòng. Vì vậy, làm theo kiểu Weirich cuối cùng trông gần giống với kiểu Rails.


1

Tôi đã sử dụng kiểu Weirich trong nhiều năm, nhưng chỉ cần chuyển từ kiểu này để luôn sử dụng niềng răng . Tôi không nhớ đã bao giờ sử dụng thông tin từ kiểu khối chưa và định nghĩa này hơi mơ hồ. Ví dụ:

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

Những điều này là procudual hay chức năng?

Và điều đếm dòng chỉ là vô ích theo ý kiến ​​của tôi. Tôi biết, cho dù có 1 hay nhiều dòng và chính xác tại sao tôi phải thay đổi kiểu chỉ vì tôi đã thêm hoặc bớt dòng?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.