Використання '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' у цьому сценарії. Чи можливо це? Я спробував перетворити блок на лямбда, але результат той самий.


чому ви хочете уникати неявного поверненого значення?
marcgg

@marcgg - у мене тут пов’язане запитання - stackoverflow.com/questions/25953519/… .
sid smith

Відповіді:


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був викликаний

4
перерва в процедурі викличе виняток
gfreezy

Ви можете цитувати, звідки ви отримуєте цю інформацію з того, що "наступне повертає значення з блоку і закінчує його викликом". Я хочу прочитати більше про це.
user566245

Це було з книги « Мова програмування Ruby» (я її зараз не маю під рукою), якщо я правильно пам’ятаю. Я щойно перевірив google і вважаю, що це з цієї книги: librairie.immateriel.fr/fr/read_book/9780596516178/… та ще 2 наступні сторінки звідти (це не мій вміст, а мої сторінки, я просто погуглив). Але я справді рекомендую оригінальну книгу, у ній набагато більше пояснених самоцвітів.
MBO

Також я відповів із голови, лише перевіряючи речі в irb, тому моя відповідь не є технічною чи повною. Для отримання додаткової інформації перегляньте книгу "Мова програмування Ruby".
MBO

Я хотів би, щоб ця відповідь була вгорі. Я не можу підтримати його достатньо.
btx9000

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
# ...

Там є докладна стаття про повернення з блоку / пуття тут
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

але це невизначена територія для мене, тому я дотримуватимусь того, що тестував до цього часу.


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
}

Цей хак дозволяє користувачам використовувати повернення в своїх процесах без наслідків, самозбереження тощо.

Перевага використання Thread тут полягає в тому, що в деяких випадках ви не отримаєте LocalJumpError - і повернення відбудеться в найнесподіванішому місці (поряд із методом верхнього рівня, несподівано пропускаючи решту його тіла).

Основний недолік - потенційні накладні витрати (ви можете замінити приєднання Thread + лише на те, yieldякщо цього достатньо у вашому сценарії).


1

Я знайшов спосіб, але він передбачає визначення методу як проміжного кроку:

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

thing { return 6 * 7 }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.