Я пытаюсь использовать 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» в этом сценарии. Это возможно? Я пробовал преобразовать блок в лямбду, но результат тот же.
ruby
lambda
return
proc-object
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
, тоnext
возвращает значение в строку, гдеyield
был вызванисточник
Вы не можете этого сделать в Ruby.
return
Ключевое слово всегда возвращается из метода или лямбды в текущем контексте. В блоках он будет возвращаться из метода, в котором было определено замыкание . Невозможно заставить вернуться из вызывающего метода или лямбды.Rubyspec показывает , что это действительно правильное поведение Руби (правда , не реальное осуществление, но цели полной совместимости с 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 }
источник
Где вызывается вещь? Вы в классе?
Вы можете использовать что-то вроде этого:
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
источник
У меня была такая же проблема с написанием DSL для веб-фреймворка на ruby ... (веб-фреймворк Anorexic будет качать!) ...
в любом случае, я покопался во внутренностях рубина и нашел простое решение, используя LocalJumpError, возвращаемый при возврате вызовов Proc ... пока что он хорошо работает в тестах, но я не уверен, что это полностью доказано:
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 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 }
Этот хак позволяет пользователям без последствий использовать return в своих процессах, сохранять себя и т. Д.
Преимущество использования Thread здесь заключается в том, что в некоторых случаях вы не получите LocalJumpError - и возврат произойдет в самом неожиданном месте (внутри метода верхнего уровня, неожиданно пропустив остальную часть его тела).
Главный недостаток - это потенциальные накладные расходы (вы можете заменить соединение Thread + только на,
yield
если этого достаточно в вашем сценарии).источник
Я нашел способ, но он включает определение метода как промежуточного шага:
def thing(*args, &block) define_method(:__thing, &block) puts "value=#{__thing}" end thing { return 6 * 7 }
источник