ChatGPT解决这个技术问题 Extra ChatGPT

Using 'return' in a Ruby block

I'm trying to use Ruby 1.9.1 for an embedded scripting language, so that "end-user" code gets written in a Ruby block. One issue with this is that I'd like the users to be able to use the 'return' keyword in the blocks, so they don't need to worry about implicit return values. With this in mind, this is the kind of thing I'd like to be able to do:

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

thing {
  return 6 * 7
}

If I use 'return' in the above example, I get a LocalJumpError. I'm aware that this is because the block in question is a Proc and not a lambda. The code works if I remove 'return', but I'd really prefer to be able to use 'return' in this scenario. Is this possible? I've tried converting the block to a lambda, but the result is the same.

why do you want to avoid an implicit return value?
@marcgg - I have a related question here - stackoverflow.com/questions/25953519/… .

M
MBO

Simply use next in this context:

$ 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 always returns from method, but if you test this snippet in irb you don't have method, that's why you have LocalJumpError

break returns value from block and ends its call. If your block was called by yield or .call, then break breaks from this iterator too

next returns value from block and ends its call. If your block was called by yield or .call, then next returns value to line where yield was called


break in a proc will raise an exception
can you cite where you get this information from that "next returns value from block and ends it call". I want to read more on it.
It was from The Ruby Programming Language book (I don't have it at hand right now) if I remember correctly. I just checked google and I believe it's from that book: librairie.immateriel.fr/fr/read_book/9780596516178/… and 2 next pagex from there (it's not my content and my pages, I just googled it). But I really recommend original book, it has much more gems explained.
Also I answered from my head, only checking things in irb, that's why my answer is not technical or complete. For more informations check The Ruby Programming Language book.
I wish this answer was at the top. I can not upvote it enough.
r
radiantRazor

You cannot do that in Ruby.

The return keyword always returns from the method or lambda in the current context. In blocks, it will return from the method in which the closure was defined. It cannot be made to return from the calling method or lambda.

The Rubyspec demonstrates that this is indeed the correct behaviour for Ruby (admittedly not a real implementation, but aims full compatibility with 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
# ...

There's a detailed article on returning from a block/proc here
S
Simone Carletti

You are looking it from the wrong point of view. This is an issue of thing, not the lambda.

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

thing {
  6 * 7
}

V
Valery

I admire the answer of s12chung. Here is my little improvement of his answer. It lets avoid cluttering the context with method __thing.

def thing(*args, &block)
  o = Object.new
  o.define_singleton_method(:__thing, block)
  puts "value=#{o.__thing}"
end

thing { return 6 * 7 }

M
Myst

I had the same issue writing a DSL for a web framework in ruby... (the web framework Anorexic will rock!)...

anyway, I dug into the ruby internals and found a simple solution using the LocalJumpError returned when a Proc calls return... it runs well in the tests so far, but I'm not sure it's full-proof:

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

the if statement in the rescue segment could probably look something like this:

if e.is_a? LocalJumpError

but it's uncharted territory for me, so I'll stick to what I tested so far.


s
s12chung

I found a way, but it involves defining a method as an intermediate step:

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

thing { return 6 * 7 }

g
giorgian

Where is thing invoked? Are you inside a class?

You may consider using something like this:

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

C
Cezary Baginski

I believe this is the correct answer, despite the drawbacks:

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
}

This hack allows users to use return in their procs without consequences, self is preserved, etc.

The advantage of using Thread here is that in some cases you won't get the LocalJumpError - and the return will happen in the most unexpected place (onside a top-level method, unexpectedly skipping the rest of it's body).

The main disadvantage is the potential overhead (you can replace the Thread+join with just the yield if that's enough in your scenario).