web-dev-qa-db-ja.com

Rubyブロックで 'return'を使用する

組み込みスクリプト言語にRuby 1.9.1を使用しようとしています。そのため、「エンドユーザー」コードはRubyブロックに記述されます。これの問題は、ユーザーがブロックで「return」キーワードを使用できるようにしたいので、暗黙的な戻り値を心配する必要がないことです。これを念頭に置いて、これは私がそうすることですできるようにしたい:

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

thing {
  return 6 * 7
}

上記の例で「return」を使用すると、LocalJumpErrorが発生します。これは、問題のブロックがラムダではなくProcであるためです。 「return」を削除するとコードは機能しますが、このシナリオでは「return」を使用できるようになりたいと思っています。これは可能ですか?ブロックをラムダに変換しようとしましたが、結果は同じです。

81
MetaFu

このコンテキストでは、単に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によって呼び出された場合、nextyieldが呼び出された行に値を返します
163
MBO

Rubyではできません。

returnキーワードalwaysは、現在のコンテキストのメソッドまたはラムダから返されます。ブロックでは、クロージャがdefinedであったメソッドから返されます。 callingメソッドまたはラムダから戻ることはできません。

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
# ...
18
molf

あなたは間違った視点からそれを見ています。これは、ラムダではなく、thingの問題です。

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

thing {
  6 * 7
}
3
Simone Carletti

欠点があるにもかかわらず、これは正しい答えだと思います。

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
}

このハックにより、ユーザーは結果に問題なくProcでリターンを使用したり、自己を保持したりすることができます。

ここでThreadを使用する利点は、LocalJumpErrorが発生しない場合があることです-そして、最も予期しない場所でリターンが発生することです(トップレベルメソッドの場合、予期せず残りのボディをスキップします)。

主な欠点は潜在的なオーバーヘッドです(シナリオで十分な場合は、Thread + joinをyieldに置き換えることができます)。

1
Cezary Baginski

何が呼び出されますか?あなたはクラスの中にいますか?

次のようなものを使用することを検討できます。

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
giorgian

RubyでWebフレームワーク用のDSLを書くのと同じ問題がありました...(WebフレームワークAnorexicは揺れ動くでしょう!)...

とにかく、私はRuby internalsを掘り下げ、Procがreturnを呼び出したときに返される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

しかし、それは私にとって未知の領域なので、これまでにテストしたものに固執します。

0
Myst

方法を見つけましたが、中間ステップとしてメソッドを定義する必要があります。

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

thing { return 6 * 7 }
0
s12chung