Używanie „return” w bloku Rubiego

87

Próbuję użyć Ruby 1.9.1 jako osadzonego języka skryptowego, tak aby kod „użytkownika końcowego” został zapisany w bloku Ruby. Jednym z problemów jest to, że chciałbym, aby użytkownicy mogli używać słowa kluczowego „return” w blokach, aby nie musieli martwić się o niejawne wartości zwracane. Mając to na uwadze, chciałbym móc to zrobić:

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

thing {
  return 6 * 7
}

Jeśli użyję „return” w powyższym przykładzie, otrzymam LocalJumpError. Zdaję sobie sprawę, że dzieje się tak, ponieważ omawiany blok jest proc, a nie lambda. Kod działa, jeśli usunę słowo „powrót”, ale naprawdę wolałbym móc użyć „powrotu” w tym scenariuszu. czy to możliwe? Próbowałem przekonwertować blok na lambdę, ale wynik jest taki sam.

MetaFu
źródło
dlaczego chcesz uniknąć niejawnej wartości zwracanej?
marcgg
@marcgg - Mam tutaj powiązane pytanie - stackoverflow.com/questions/25953519/… .
sid smith

Odpowiedzi:

171

Po prostu użyj nextw tym kontekście:

$ 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 zawsze zwraca z metody, ale jeśli testujesz ten fragment kodu w irb, nie masz metody, to dlatego ją masz LocalJumpError
  • breakzwraca wartość z bloku i kończy jego wywołanie. Jeśli twój blok został wywołany przez yieldlub .call, to również breakprzerywa działanie tego iteratora
  • nextzwraca wartość z bloku i kończy jego wywołanie. Jeśli twój blok został wywołany przez yieldlub .call, to nextzwraca wartość do wiersza, w którym yieldzostał wywołany
MBO
źródło
4
przerwa w
procu
czy możesz zacytować, skąd czerpiesz te informacje z tego „next zwraca wartość z bloku i kończy połączenie”. Chcę przeczytać więcej na ten temat.
user566245,
To było z książki The Ruby Programming Language (nie mam go teraz pod ręką), jeśli dobrze pamiętam. Właśnie sprawdziłem google i wydaje mi się, że to z tej książki: librairie.immateriel.fr/fr/read_book/9780596516178/ ... i 2 następna stronax stamtąd (to nie jest moja treść i moje strony, po prostu googlowałem). Ale naprawdę polecam oryginalną książkę, ma o wiele więcej wyjaśnionych perełek.
MBO,
Odpowiadałem też z głowy, sprawdzając tylko rzeczy w irbie, dlatego moja odpowiedź nie jest techniczna ani kompletna. Więcej informacji znajdziesz w książce The Ruby Programming Language.
MBO,
Chciałbym, żeby ta odpowiedź była na górze. Nie mogę tego wystarczająco zagłosować.
btx9000
20

Nie możesz tego zrobić w Rubim.

Słowo returnkluczowe zawsze zwraca z metody lub lambda w bieżącym kontekście. W blokach zwróci z metody, w której zostało zdefiniowane zamknięcie . Nie można sprawić, aby powrócił z metody wywołującej lub lambdy.

Rubyspec pokazuje, że jest to rzeczywiście poprawne zachowanie dla Ruby (co prawda nie jest prawdziwa realizacja, ale cele pełna kompatybilność z 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
źródło
Szczegółowy artykuł na temat powrotu z bloku / proc tutaj
ComDubh
3

Patrzysz na to z niewłaściwego punktu widzenia. To jest problem thing, a nie lambda.

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

thing {
  6 * 7
}
Simone Carletti
źródło
1

Gdzie jest powołana rzecz? Czy jesteś w klasie?

Możesz rozważyć użycie czegoś takiego:

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
giorgian
źródło
1

Miałem ten sam problem, pisząc DSL dla frameworka internetowego w Rubim ... (framework sieciowy Anorexic zadziała!) ...

w każdym razie, zagłębiłem się w wewnętrzne elementy ruby ​​i znalazłem proste rozwiązanie, używając LocalJumpError zwracanego, gdy wywołania Proc wracają ... działa dobrze w dotychczasowych testach, ale nie jestem pewien, czy jest w pełni sprawdzony:

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

stwierdzenie if w segmencie ratunkowym mogłoby prawdopodobnie wyglądać mniej więcej tak:

if e.is_a? LocalJumpError

ale jest to dla mnie niezbadane terytorium, więc będę się trzymał tego, co testowałem do tej pory.

Myst
źródło
1

Uważam, że jest to prawidłowa odpowiedź, pomimo wad:

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
}

Ten hack pozwala użytkownikom używać return w ich procesach bez konsekwencji, self jest zachowane itp.

Zaletą korzystania z Thread w tym miejscu jest to, że w niektórych przypadkach nie otrzymasz LocalJumpError - a zwrot nastąpi w najbardziej nieoczekiwanym miejscu (w przypadku metody najwyższego poziomu, nieoczekiwanie pomijając resztę jej treści).

Główną wadą jest potencjalny narzut (możesz zastąpić połączenie Thread + tylko tym, yieldjeśli to wystarczy w Twoim scenariuszu).

Cezary Bagiński
źródło
1

Znalazłem sposób, ale wymaga to zdefiniowania metody jako kroku pośredniego:

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

thing { return 6 * 7 }
s12chung
źródło