Использование return в блоке Ruby - PullRequest
81 голосов
/ 24 февраля 2010

Я пытаюсь использовать 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» в этом сценарии. Это возможно? Я пытался преобразовать блок в лямбду, но результат тот же.

Ответы [ 7 ]

163 голосов
/ 24 февраля 2010

Просто используйте 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 был вызван
18 голосов
/ 24 февраля 2010

Вы не можете сделать это в 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 голосов
/ 24 февраля 2010

Вы смотрите с неправильной точки зрения. Это проблема thing, а не лямбда.

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

thing {
  6 * 7
}
1 голос
/ 27 мая 2015

Я считаю, что это правильный ответ, несмотря на недостатки:

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, если этого достаточно в вашем сценарии).

1 голос
/ 24 февраля 2010

Где вызывается вещь? Вы в классе?

Вы можете использовать что-то вроде этого:

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
0 голосов
/ 27 июля 2015

Я нашел способ, но он включает определение метода в качестве промежуточного шага:

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

thing { return 6 * 7 }
0 голосов
/ 12 октября 2014

У меня была такая же проблема при написании 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

но для меня это неизведанная территория, поэтому я буду придерживаться того, что я до сих пор тестировал.

...