2010-09-15 33 views
30

Trong hầu hết các ngôn ngữ khác, câu lệnh bắt và ném làm những gì câu lệnh bắt đầu, giải cứu và nâng cao thực hiện trong Ruby. Tôi biết các bạn có thể làm điều này với hai báo cáo:Bắt và ném được sử dụng trong Ruby là gì?

catch :done do 
    puts "I'm done." 
end 

if some_condition 
    throw :done 
end 

Nhưng điều này hữu ích cho? Có thể ai đó xin vui lòng cho tôi một ví dụ về những gì bắt và ném báo cáo được sử dụng trong Ruby?

+1

Bản sao có thể có của: http://stackoverflow.com/questions/51021/what-is-the-differance-between-raising-exceptions-vs-throwing-exceptions-in-ruby – Shadwell

Trả lời

32

Bạn có thể sử dụng điều này để thoát khỏi vòng lặp lồng nhau.

INFINITY = 1.0/0.0 
catch (:done) do 
    1.upto(INFINITY) do |i| 
    1.upto(INFINITY) do |j| 
     if some_condition 
     throw :done 
     end 
    end 
    end 
end 

Nếu bạn đã sử dụng tuyên bố ngắt ở trên, nó sẽ bị vỡ ra khỏi vòng lặp bên trong. Nhưng nếu bạn muốn thoát khỏi vòng lặp lồng nhau thì cú đánh/ném này sẽ thực sự hữu ích. Tôi đã sử dụng nó here để giải quyết một trong các vấn đề về Euler.

21

Tôi đã tìm kiếm một ví dụ điển hình trong một thời gian, cho đến khi tôi gặp Sinatra. IMHO, Sinatra cho thấy một ví dụ sử dụng ví dụ rất thú vị cho catch.

Ở Sinatra, bạn có thể immediately terminate a request bất kỳ lúc nào bằng cách sử dụng halt.

halt 

Bạn cũng có thể xác định trạng thái khi ngăn chặn ...

halt 410 

Hoặc cơ thể ...

halt 'this will be the body' 

Hoặc cả hai ...

halt 401, 'go away!' 

Phương pháp tạm dừng là implemented using throw.

def halt(*response) 
    response = response.first if response.length == 1 
    throw :halt, response 
end 

caught by phương thức invoke.

Có một số công dụng của :halt ở Sinatra. Bạn có thể đọc mã nguồn để biết thêm ví dụ.

+1

tự hỏi, làm thế nào điều này tốt hơn hơn là sử dụng ngoại lệ? – jsz

+2

Ném nó là một cách thanh lịch hơn để sử dụng một hệ thống giống như ngoại lệ như một luồng điều khiển. –

+2

@ jsz cho một, nó nhanh hơn nhiều - khung ngăn xếp không phải được thực hiện dọc theo "biểu tượng ném", và không có đối tượng được tạo ra. Kiểm soát dòng chảy phi tuyến nhẹ. – mezis

1

Khi viết các thuật toán đệ quy mà hành động dựa trên cấu trúc dữ liệu lồng nhau sử dụng các hàm đệ quy, bạn có thể sử dụng throw tương tự như cách bạn sẽ sử dụng một break hoặc đầu return khi viết các thuật toán lặp đi lặp lại rằng hành động trên dữ liệu phẳng bằng for vòng.

Ví dụ, giả sử rằng bạn có một danh sách các số nguyên dương và bạn muốn (đối với một số lý do) để viết một hàm trả về true nếu một trong các điều kiện sau đây được đáp ứng:

  • Tổng số tất cả các yếu tố trong danh sách là lớn hơn 100
  • một số phần tử trong danh sách nếu bằng 5

Hãy nói cũng rằng bạn luôn muốn thực hiện việc kiểm tra này trong một duy nhất, ngắn mạch đường chuyền qua danh sách, thay vì làm một số reduce c tất cả để có được số tiền và một cuộc gọi any? riêng biệt để tìm kiếm fives.

Có thể bạn muốn viết một số mã một chút như thế này (thực sự, có lẽ bạn đã viết code như thế này trong một số ngôn ngữ tại một số điểm trong cuộc sống của bạn):

def test(list) 
    sum = 0 
    for i in list 
    sum += i 
    if i == 5 || sum > 100 
     return true 
    end 
    end 
    return false 
end 

Trong hầu hết các ngôn ngữ, không có tương đương sạch để loại bỏ thuật toán đệ quy sử dụng hàm đệ quy. Trong Ruby, mặc dù, có! Giả sử rằng, thay vì có danh sách và muốn kiểm tra xem các phần tử của nó có chứa năm hoặc tổng thành hơn 100 hay không, bạn có một cây và muốn kiểm tra xem lá của mình có chứa năm hoặc tổng trên 100 hay không -circuiting và trở lại ngay sau khi bạn biết câu trả lời.

Bạn có thể làm điều này thanh lịch với throw/catch, như thế này:

def _test_recurse(sum_so_far, node) 
    if node.is_a? InternalNode 
    for child_node in node.children 
     sum_so_far = _test_recurse(sum_so_far, child_node) 
    end 
    return sum_so_far 
    else # node.is_a? Leaf 
    sum_so_far += node.value 
    if node.value == 5 
     throw :passes_test 
    elsif sum_so_far > 100 
     throw :passes_test 
    else 
     return sum_so_far 
    end 
    end 
end 

def test(tree)    
    catch (:passes_test) do 
    _test_recurse(0, tree) 
    return false 
    end 
    return true 
end 

Các throw :passes_test đây đóng vai trò giống như một break; nó cho phép bạn nhảy ra khỏi toàn bộ ngăn xếp cuộc gọi của bạn bên dưới cuộc gọi _test ngoài cùng bên ngoài. Trong các ngôn ngữ khác, bạn có thể làm điều này bằng cách lạm dụng ngoại lệ cho mục đích này hoặc bằng cách sử dụng một số mã trả về để báo cho hàm đệ quy ngừng đệ quy, nhưng điều này trực tiếp và đơn giản hơn.

Các vấn đề liên quan