Использование 'return' в блоке Ruby
Я пытаюсь использовать 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' в этом сценарии. Это возможно? Я попытался преобразовать блок в лямбда, но результат тот же.
Ответы
Ответ 1
Просто используйте 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
был вызван
Ответ 2
Вы не можете сделать это в Ruby.
Ключевое слово return
всегда возвращается из метода или лямбда в текущем контексте. В блоках он будет возвращен из метода, в котором было определено замыкание. Нельзя заставить вернуться из метода вызова или лямбда.
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
# ...
Ответ 3
Вы смотрите на него с неправильной точки зрения.
Это проблема thing
, а не лямбда.
def thing(*args, &block)
block.call.tap do |value|
puts "value=#{value}"
end
end
thing {
6 * 7
}
Ответ 4
Где вызвана вещь? Вы внутри класса?
Вы можете использовать что-то вроде этого:
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
Ответ 5
Я считаю, что это правильный ответ, несмотря на недостатки:
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
}
Этот хак позволяет пользователям использовать возврат в своих процедурах без последствий, само сохранение и т.д.
Преимущество использования Thread здесь в том, что в некоторых случаях вы не получите LocalJumpError - и возврат произойдет в самом неожиданном месте (рядом с методом верхнего уровня, неожиданно пропуская остальную часть его тела).
Основным недостатком является потенциальная накладная (вы можете заменить Thread + join только yield
, если это достаточно в вашем сценарии).
Ответ 6
У меня была та же проблема, что и запись DSL для веб-фреймворка в ruby ... (веб-фреймворк Anorexic будет качать!)...
В любом случае, я ворвался в внутренности ruby и нашел простое решение, используя 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
но это неизведанная территория для меня, поэтому я буду придерживаться того, что я тестировал до сих пор.
Ответ 7
Я нашел способ, но он включает определение метода как промежуточного шага:
def thing(*args, &block)
define_method(:__thing, &block)
puts "value=#{__thing}"
end
thing { return 6 * 7 }