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


87

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

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

thing {
  return 6 * 7
}

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


暗黙の戻り値を避けたいのはなぜですか?
marcgg 2010

@marcgg -私はここに関連する質問を持っている- stackoverflow.com/questions/25953519/...を
sid smith

回答:


171

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またはによって呼び出された場合は.callbreakこのイテレータからも切り離されます
  • nextブロックから値を返し、呼び出しを終了します。あなたのブロックは、次のように呼び出した場合yield.call、その後nextの行に値を返すyieldと呼ばれていました

4
procを中断すると、例外が発生します
gfreezy 2012

「次はブロックから値を返し、呼び出しを終了する」という情報からこの情報をどこで取得するかを引用できますか。もっと読みたいです。
user566245 2013

私が正しく覚えていれば、それはRubyプログラミング言語の本(私は今手元にありません)からのものでした。私はちょうどグーグルをチェックしました、そしてそれはその本からのものであると信じています:librairie.immateriel.fr/fr/read_book/9780596516178/…そしてそこから2つの次のpagex(それは私のコンテンツと私のページではなく、私はそれをググっただけです)。しかし、私は本当にオリジナルの本をお勧めします、それははるかに多くの宝石が説明されています。
MBO

また、私は頭から答えましたが、irbで物事をチェックするだけでした。そのため、私の答えは技術的でも完全でもありません。詳細については、Rubyプログラミング言語の本を確認してください。
MBO

この答えが一番上にあったらいいのにと思います。私はそれを十分に賛成することはできません。
btx9000 2015

20

Rubyではそれはできません。

returnキーワードは、常に現在のコンテキスト内のメソッドまたはラムダから返します。ブロックでは、クロージャーが定義されたメソッドから戻ります。呼び出し元のメソッドまたはラムダから戻ることはできません。

ザ・ Rubyspecが、これは確かにRuby用の正しい動作であることを実証している(確かではない実際の実装が、Cルビーとの完全な互換性を目指して):

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

ブロック/
プロシージャ

3

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

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

thing {
  6 * 7
}

1

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

次のようなものの使用を検討してください。

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

私はルビーでウェブフレームワークのDSLを書くのと同じ問題を抱えていました...(ウェブフレームワークAnorexicは揺れるでしょう!)...

とにかく、私はルビーの内部を掘り下げて、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

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


1

欠点はありますが、これが正解だと思います。

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でreturnを使用でき、自己保存などが可能になります。

ここでThreadを使用する利点は、LocalJumpErrorが発生しない場合があり、最も予期しない場所で戻りが発生することです(トップレベルのメソッドの横で、予期せず本体の残りの部分をスキップします)。

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


1

私は方法を見つけましたが、それは中間ステップとしてメソッドを定義することを含みます:

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

thing { return 6 * 7 }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.