Sử dụng 'return' trong một khối Ruby


87

Tôi đang cố gắng sử dụng Ruby 1.9.1 cho một ngôn ngữ kịch bản nhúng để mã "người dùng cuối" được viết trong một khối Ruby. Một vấn đề với điều này là tôi muốn người dùng có thể sử dụng từ khóa 'return' trong các khối, vì vậy họ không cần phải lo lắng về các giá trị trả về ngầm định. Với suy nghĩ này, đây là loại điều tôi muốn có thể làm:

def thing(*args, &block)
  value = block.call
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

Nếu tôi sử dụng 'return' trong ví dụ trên, tôi nhận được lỗi LocalJumpError. Tôi biết rằng điều này là do khối được đề cập là một Proc chứ không phải lambda. Mã hoạt động nếu tôi loại bỏ 'return', nhưng tôi thực sự muốn có thể sử dụng 'return' trong trường hợp này. Điều này có khả thi không? Tôi đã thử chuyển đổi khối thành lambda, nhưng kết quả vẫn vậy.


tại sao bạn lại muốn tránh một giá trị trả về ngầm định?
marcgg 24/02/10

@marcgg - Tôi có một câu hỏi liên quan ở đây - stackoverflow.com/questions/25953519/… .
Sid smith

Câu trả lời:


171

Chỉ cần sử dụng nexttrong ngữ cảnh này:

$ irb
irb(main):001:0> def thing(*args, &block)
irb(main):002:1>   value = block.call
irb(main):003:1>   puts "value=#{value}"
irb(main):004:1> end
=> nil
irb(main):005:0>
irb(main):006:0* thing {
irb(main):007:1*   return 6 * 7
irb(main):008:1> }
LocalJumpError: unexpected return
        from (irb):7:in `block in irb_binding'
        from (irb):2:in `call'
        from (irb):2:in `thing'
        from (irb):6
        from /home/mirko/.rvm/rubies/ruby-1.9.1-p378/bin/irb:15:in `<main>'
irb(main):009:0> thing { break 6 * 7 }
=> 42
irb(main):011:0> thing { next 6 * 7 }
value=42
=> nil
  • return luôn trả về từ phương thức, nhưng nếu bạn kiểm tra đoạn mã này trong irb, bạn không có phương thức, đó là lý do tại sao bạn có LocalJumpError
  • breaktrả về giá trị từ khối và kết thúc cuộc gọi của nó. Nếu khối của bạn được gọi bằng yieldhoặc .call, thì cũng breakngắt từ trình lặp này
  • nexttrả về giá trị từ khối và kết thúc cuộc gọi của nó. Nếu khối của bạn được gọi bằng yieldhoặc .call, thì nexttrả về giá trị cho dòng nơi yieldđược gọi

4
đột nhập một proc sẽ tăng một ngoại lệ
gfreezy

bạn có thể trích dẫn nơi bạn lấy thông tin này từ đó "tiếp theo trả về giá trị từ khối và kết thúc lệnh gọi". Tôi muốn đọc thêm về nó.
user566245,

Nó được lấy từ cuốn sách Ngôn ngữ lập trình Ruby (tôi không có nó ngay bây giờ) nếu tôi nhớ không nhầm. Tôi chỉ cần kiểm tra google và tôi tin rằng đó là từ cuốn sách đó: librairie.immateriel.fr/fr/read_book/9780596516178/... và 2 Trang X tiếp theo từ đó (nó không phải là nội dung của tôi và các trang của tôi, tôi chỉ googled nó). Nhưng tôi thực sự khuyên bạn nên cuốn sách gốc, nó có nhiều đá quý hơn được giải thích.
MBO

Ngoài ra, tôi đã trả lời từ đầu của mình, chỉ kiểm tra mọi thứ trong irb, đó là lý do tại sao câu trả lời của tôi không phải là kỹ thuật hoặc hoàn chỉnh. Để biết thêm thông tin, hãy xem sách Ngôn ngữ lập trình Ruby.
MBO

Tôi ước câu trả lời này ở trên cùng. Tôi không thể ủng hộ nó đủ.
btx9000

20

Bạn không thể làm điều đó trong Ruby.

Các returntừ khóa luôn trả về từ phương pháp hoặc lambda trong bối cảnh hiện nay. Trong các khối, nó sẽ trả về từ phương thức mà việc đóng đã được xác định . Nó không thể được thực hiện để trả về từ phương thức gọi hoặc lambda.

Các Rubyspec chứng minh rằng điều này thực sự là hành vi đúng cho Ruby (phải thừa nhận là không phải là một thực hiện thực, nhưng mục đích tương thích đầy đủ với C Ruby):

describe "The return keyword" do
# ...
describe "within a block" do
# ...
it "causes the method that lexically encloses the block to return" do
# ...
it "returns from the lexically enclosing method even in case of chained calls" do
# ...

Có một bài viết chi tiết về việc quay trở lại từ khối / proc ở đây
ComDubh

3

Bạn đang nhìn nó từ một quan điểm sai lầm. Đây là một vấn đề của thing, không phải lambda.

def thing(*args, &block)
  block.call.tap do |value|
    puts "value=#{value}"
  end
end

thing {
  6 * 7
}

1

Điều được gọi ở đâu? Bạn đang ở trong một lớp học?

Bạn có thể xem xét sử dụng một cái gì đó như thế này:

class MyThing
  def ret b
    @retval = b
  end

  def thing(*args, &block)
    implicit = block.call
    value = @retval || implicit
    puts "value=#{value}"
  end

  def example1
    thing do
      ret 5 * 6
      4
    end
  end

  def example2
    thing do
      5 * 6
    end
  end
end

1

Tôi đã gặp vấn đề tương tự khi viết DSL cho một khuôn khổ web bằng ruby ​​... (khuôn khổ web Anorexic sẽ làm rung chuyển!) ...

Dù sao đi nữa, tôi đã đào sâu vào bên trong của ruby ​​và tìm thấy một giải pháp đơn giản bằng cách sử dụng LocalJumpError được trả lại khi một lệnh gọi Proc trả về ... nó chạy tốt trong các thử nghiệm cho đến nay, nhưng tôi không chắc là nó có đầy đủ bằng chứng:

def thing(*args, &block)
  if block
    block_response = nil
    begin
      block_response = block.call
    rescue Exception => e
       if e.message == "unexpected return"
          block_response = e.exit_value
       else
          raise e 
       end
    end
    puts "value=#{block_response}"
  else
    puts "no block given"
  end
end

câu lệnh if trong phân đoạn giải cứu có thể trông giống như sau:

if e.is_a? LocalJumpError

nhưng đó là lãnh thổ chưa được khám phá đối với tôi, vì vậy tôi sẽ bám sát những gì tôi đã thử nghiệm cho đến nay.


1

Tôi tin rằng đây là câu trả lời chính xác, mặc dù có những hạn chế:

def return_wrap(&block)
  Thread.new { return yield }.join
rescue LocalJumpError => ex
  ex.exit_value
end

def thing(*args, &block)
  value = return_wrap(&block)
  puts "value=#{value}"
end

thing {
  return 6 * 7
}

Hack này cho phép người dùng sử dụng trả lại trong procs của họ mà không có hậu quả, bản thân được bảo toàn, v.v.

Lợi thế của việc sử dụng Thread ở đây là trong một số trường hợp, bạn sẽ không nhận được LocalJumpError - và việc trả lại sẽ xảy ra ở nơi không mong đợi nhất (bên trong một phương thức cấp cao nhất, bất ngờ bỏ qua phần còn lại của nó).

Bất lợi chính là chi phí tiềm ẩn (bạn có thể thay thế Thread + join chỉ bằng yieldnếu điều đó là đủ trong kịch bản của bạn).


1

Tôi đã tìm ra một cách, nhưng nó liên quan đến việc xác định một phương pháp như một bước trung gian:

def thing(*args, &block)
  define_method(:__thing, &block)
  puts "value=#{__thing}"
end

thing { return 6 * 7 }
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.