使用“返回”;在 Ruby 块中

发布于 2024-08-22 12:55:38 字数 419 浏览 6 评论 0原文

我正在尝试使用 Ruby 1.9.1 作为嵌入式脚本语言,以便“最终用户”代码可以在 Ruby 块中编写。这样做的一个问题是,我希望用户能够在块中使用“return”关键字,这样他们就不必担心隐式返回值。考虑到这一点,这就是我希望能够做的事情:

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

thing {
  return 6 * 7
}

如果我在上面的示例中使用“return”,我会得到一个 LocalJumpError。我知道这是因为有问题的块是 Proc 而不是 lambda。如果我删除“return”,该代码就可以工作,但我真的更希望能够在这种情况下使用“return”。这可能吗?我尝试将块转换为 lambda,但结果是相同的。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

江挽川 2024-08-29 12:55:38

只需在这种情况下使用 next 即可:

$ 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 总是从方法返回,但是如果您在 irb 中测试此代码片段,则没有方法,这就是为什么您有 LocalJumpError
  • break 从块返回值并结束其调用。如果您的块是由 yield.call 调用的,则 break 也会从此迭代器中断
  • next 返回值阻止并结束其调用。如果您的块是由 yield.call 调用的,则 next 将值返回到调用 yield 的行

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
一腔孤↑勇 2024-08-29 12:55:38

在 Ruby 中你不能这样做。

return 关键字总是从当前上下文中的方法或 lambda 返回。在块中,它将从定义闭包的方法返回。它无法从调用方法或 lambda 中返回。

Rubyspec 表明这确实是 Ruby 的正确行为(诚然,这不是一个真正的实现,但目标是与 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
# ...

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
# ...
赠意 2024-08-29 12:55:38

我很欣赏 s12chung 的答案。这是我对他的答案的一点改进。它可以避免方法 __thing 使上下文混乱。

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

thing { return 6 * 7 }

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 }
依 靠 2024-08-29 12:55:38

你从错误的角度看待它。
这是一个 thing 的问题,而不是 lambda 的问题。

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

thing {
  6 * 7
}

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
}
画▽骨i 2024-08-29 12:55:38

我在用 ruby​​ 为 Web 框架编写 DSL 时遇到了同样的问题...(Web 框架 Anorexic 会摇滚!)...

无论如何,我深入研究了 ruby​​ 内部结构并找到了一个简单的解决方案,使用 Proc 调用时返回的 LocalJumpError返回...到目前为止,它在测试中运行良好,但我不确定它是否完全证明:

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

救援部分中的 if 语句可能看起来像这样:

if e.is_a? LocalJumpError

但这对我来说是未知的领域,所以我会坚持下去到目前为止我测试过的。

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.

站稳脚跟 2024-08-29 12:55:38

我找到了一种方法,但它涉及定义一个方法作为中间步骤:

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

thing { return 6 * 7 }

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 }
同展鸳鸯锦 2024-08-29 12:55:38

事物在哪里被调用?你在班级里吗?

您可以考虑使用这样的东西:

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

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
蓝眼泪 2024-08-29 12:55:38

我相信这是正确的答案,尽管有缺点:

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 允许用户在他们的过程中使用 return 而不会产生任何后果, self 被保留,等等。

在这里使用 Thread 的优点是,在某些情况下你不会得到 LocalJumpError -并且返回将发生在最意想不到的地方(在顶级方法上,意外地跳过其主体的其余部分)。

主要缺点是潜在的开销(如果您的场景足够的话,您可以仅使用 yield 替换 Thread+join)。

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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文