組み込みスクリプト言語に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」を使用できるようになりたいと思っています。これは可能ですか?ブロックをラムダに変換しようとしましたが、結果は同じです。
このコンテキストでは、単に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
が呼び出された行に値を返します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
# ...
あなたは間違った視点からそれを見ています。これは、ラムダではなく、thing
の問題です。
def thing(*args, &block)
block.call.tap do |value|
puts "value=#{value}"
end
end
thing {
6 * 7
}
欠点があるにもかかわらず、これは正しい答えだと思います。
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
に置き換えることができます)。
何が呼び出されますか?あなたはクラスの中にいますか?
次のようなものを使用することを検討できます。
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
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
しかし、それは私にとって未知の領域なので、これまでにテストしたものに固執します。
方法を見つけましたが、中間ステップとしてメソッドを定義する必要があります。
def thing(*args, &block)
define_method(:__thing, &block)
puts "value=#{__thing}"
end
thing { return 6 * 7 }