Использование return в блоке Ruby

87

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

MetaFu
источник
почему вы хотите избежать неявного возвращаемого значения?
marcgg
@marcgg - у меня есть связанный с этим вопрос - stackoverflow.com/questions/25953519/… .
Сид Смит

Ответы:

171

Просто используйте 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был вызван
MBO
источник
4
перерыв в процедуре вызовет исключение
gfreezy
Можете ли вы указать, откуда вы получаете эту информацию из того, что «следующий возвращает значение из блока и завершает его вызов». Я хочу узнать об этом подробнее.
user566245 09
Это было из книги « Язык программирования Ruby» (у меня ее сейчас нет под рукой), если я правильно помню. Я только что проверил Google и думаю, что это из той книги: librairie.immateriel.fr/fr/read_book/9780596516178/… и 2 следующих страницыx оттуда (это не мой контент и мои страницы, я просто погуглил). Но я действительно рекомендую оригинальную книгу, в ней объяснено гораздо больше драгоценных камней.
MBO
Также я отвечал из головы, проверяя только в irb, поэтому мой ответ не является техническим или полным. Для получения дополнительной информации обратитесь к книге «Язык программирования Ruby».
MBO
Я бы хотел, чтобы этот ответ был наверху. Я не могу проголосовать за это достаточно.
btx9000 01
20

Вы не можете этого сделать в 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
# ...
molf
источник
Там есть подробная статья о возвращении из блока / прока здесь
ComDubh
3

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

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

thing {
  6 * 7
}
Симоне Карлетти
источник
1

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

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

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
великан
источник
1

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

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

Myst
источник
1

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

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

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

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

thing { return 6 * 7 }
s12chung
источник