Использование '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 }