2015-08-13 17 views
5

Tôi đã tự hỏi làm cách nào để truyền một khối tới một phương thức sẽ thực hiện phương thức return trên yield.Tìm hiểu về việc quay trở lại từ procs trong Ruby

Các aproach ngây thơ không hoạt động:

def run(&block) 
    block.call 
end 

run { return :foo } # => LocalJumpError 

Wrapping trong proc khác có tác dụng tương tự:

def run(&block) 
    proc { block.call }.call 
end 

run { return :bar } # => LocalJumpError 

Vì vậy, tôi nghĩ rằng tuyên bố return được ràng buộc với receiver của dòng điện binding. Tuy nhiên, cố gắng nó ra với instance_eval chứng minh tôi sai:

class ProcTest 
    def run(&block) 
    puts "run: #{[binding.local_variables, binding.receiver]}" 
    instance_eval(&block) 
    end 
end 

pt = ProcTest.new 
binding_inspector = proc { puts "proc: #{[binding.local_variables, binding.receiver]}" } 
puts "main: #{[binding.local_variables, binding.receiver]}" 
    # => main: [[:pt, :binding_inspector], main] 
binding_inspector.call 
    # => proc: [[:pt, :binding_inspector], main] 
pt.run(&binding_inspector) 
    # => run: [[:block], #<ProcTest:0x007f4987b06508>] 
    # => proc: [[:pt, :binding_inspector], #<ProcTest:0x007f4987b06508>] 
pt.run { return :baz } 
    # => run: [[:block], #<ProcTest:0x007f4987b06508>] 
    # => LocalJumpError 

Vì vậy, câu hỏi là:

  1. Làm thế nào điều này có thể được thực hiện?
  2. Ngữ cảnh trả về được gắn với câu hỏi return. Kết nối này có thể truy cập được qua API của ngôn ngữ không?
  3. Việc này có được thực hiện theo cách như vậy không? Nếu đúng thì tại sao? Nếu không - các chướng ngại vật để sửa chữa nó là gì?

Trả lời

1

return trong khối trả về từ phương pháp kèm theo khi khối được xác định (tức là đóng cửa mà khối được tạo). Trong ví dụ của bạn, không có khối bao quanh để trả về, do đó ngoại lệ của bạn.

này có thể dễ dàng chứng minh:

def foo(&block) 
    puts yield 
    puts "we won't get here" 
end 

def bar 
    foo { return "hi from the block"; puts "we never get here" } 
    puts "we never get here either" 
end 

puts bar # => "hi from the block" (only printed once; the puts in `foo` is not executed) 

Return trong một proc sẽ ngay lập tức trở ra khỏi proc, không nằm ngoài phương pháp trên stack dưới proc:

def foo(&block) 
    puts yield 
    puts "we will get here" 
end 

def bar 
    foo &->{ return "hi from the proc"; puts "we never get here" } 
    puts "we will get here too" 
end 

puts bar 
# hi from the proc  # puts from foo 
# we will get here  # puts from foo 
# we will get here too # puts from bar 

Vì những hành vi, không có cách nào để đạt được hành vi mong muốn của bạn, trong đó một return trong khối đã cho sẽ thực hiện một phương thức mà từ đó khối được gọi, trừ khi khối được xác định trong phạm vi đó, vì làm như vậy sẽ yêu cầu một các hành vi hiện tại không hoạt động.

Bạn có thể đạt được một cái gì đó như thế này với ném ... bắt, đó là kinda-sorta hữu ích như là một cách để zip lên ngăn xếp từ một độ sâu tùy ý, nhưng bạn không thể trở về giá trị tùy ý với nó:

def foo(&block) 
    yield 
    puts "we won't get here" 
end 

catch(:escape) do 
    foo &->{ throw :escape } 
end 
+0

Bạn không trả lời một một câu hỏi. Điểm đầu tiên - tôi biết rằng tôi nhận được ngoại lệ bởi vì tôi đang cố gắng 'return' từ phạm vi * main * và' return' được gắn với phạm vi đã định nghĩa nó. Về phần thứ hai - tôi biết * lambdas * có ngữ nghĩa 'return' khác với procs * thông thường *, câu hỏi không phải về chúng. Tôi cũng biết về 'throw' - 'catch', câu hỏi là về sự hiểu biết' return'. Nó giống như hỏi * tại sao là cỏ xanh? * Và nhận được một câu trả lời * nhìn cỏ này, bạn có thể thấy rằng đó là màu xanh lá cây, nước không phải là màu xanh lá cây và bạn có thể vẽ cỏ giả trong không màu xanh lá cây *. – ndn

+0

Không có cơ sở nào để Ruby truyền một khối tới một phương thức có thể trở về từ bên trong khối, trong khi phương thức này được chuyển tới vì hành vi trả về như vậy sẽ loại trừ lẫn nhau với các hành vi trả về hiện có. Tôi không biết * tại sao * nó được thiết kế theo cách đó, nhưng "tại sao Ruby không có cái này" được trả lời, và do đó Q1 được trả lời (bạn không thể), Q3 là không thích hợp (nó không phải là lỗi để được cố định, đó là một lựa chọn có chủ ý của các lựa chọn thay thế lẫn nhau), Q2 sẽ yêu cầu nhận được vào phân tích cú pháp và mã VM để trả lời, nhưng câu trả lời có lẽ là "ngữ cảnh không được phơi bày". –

+0

Bạn có ý nghĩa gì bởi * vì hành vi trả lại như vậy sẽ loại trừ lẫn nhau với các hành vi trả lại hiện tại * và * lựa chọn có chủ ý các lựa chọn thay thế lẫn nhau *? Hành vi mong muốn nào sẽ bị hỏng nếu nó được thực hiện để trả về từ phương thức gọi. Không phải là thực thi proc thêm vào ngăn xếp khi nó được gọi là? * tại sao Ruby không có điều này * ngụ ý rằng đây là một tính năng bổ sung, mà không phải là. – ndn

2

Tôi nghĩ rằng câu hỏi return bị ràng buộc với receiver của số binding hiện tại.

Chỉ phương thức mới có bộ thu.return không phải là một phương pháp:

defined? return #=> "expression" 

Đang cố gắng để gọi nó như là một phương pháp không làm việc:

def foo 
    send(:return, 123) 
end 

foo #=> undefined method `return' 

cố gắng nó ra với instance_eval chứng minh tôi sai

Mặc dù instance_eval đánh giá khối trong ngữ cảnh của người nhận (vì vậy bạn có quyền truy cập vào các phương thức của trình thu và các biến mẫu):

class MyClass 
    def foo(&block) 
    @var = 123 
    instance_eval(&block) 
    end 
end 

MyClass.new.foo { instance_variables } 
#=> [:@var] 

... nó không đánh giá khối trong hiện ràng buộc (do đó bạn không có quyền truy cập vào bất kỳ biến địa phương):

class MyClass 
    def foo(&block) 
    var = 123 
    instance_eval(&block) 
    end 
end 

MyClass.new.foo { local_variables } 
#=> [] 

Làm thế nào điều này có thể được thực hiện ?

Bạn có thể sử dụng eval, nhưng điều đó đòi hỏi một chuỗi:

def foo 
    var = 123 
    eval yield 
    nil 
end 

foo { "return var * 2" } 
#=> 246 

Hoặc bằng cách thông qua các liên kết với các khối (một lần nữa sử dụng eval):

def foo 
    var = 123 
    yield binding 
    nil 
end 

foo { |b| b.eval "return var * 2" } 
#=> 246 
+1

eval có lẽ là giải pháp tốt nhất ở đây, vì mã được đánh giá trong phạm vi của phương pháp sinh lãi, nhưng 'eval yield' có vẻ vô cùng nguy hiểm; 'yield binding' có lẽ là appropach tốt nhất. Hãy nhận biết rằng mã này sẽ được phân tích cú pháp, biên dịch thành một iseq, và sau đó được thực thi trên mọi lời gọi, có thể ít hơn hiệu suất WRT sao - tôi đặt nó ở khoảng 100k IPS - một bậc độ lớn chậm hơn một thao tác khối không liên quan đến eval –

+1

@ChrisHeald cố gắng gọi 'return' từ bên ngoài một phương pháp có lẽ không phải là ý tưởng tốt nhất ở nơi đầu tiên ;-) – Stefan

+0

suy nghĩ của tôi là * proc * invocation chỉ cần thêm * proc * body vào stack hiện tại và trở về từ rằng * proc * sẽ trả về ngăn xếp đến thứ cuối cùng trong ngăn xếp (hay còn gọi là phương thức gọi nó). Vì đây không phải là trường hợp, tôi nghĩ rằng nơi mà 'return' sẽ bị sa thải nên được lưu trữ ở đâu đó. 'block.binding.receiver' có vẻ hợp lý. Và mặc dù 'instance_eval' không thay thế hoàn toàn ràng buộc, nó thay đổi nó (như đã thấy -' receiver' là khác nhau). – ndn

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