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.
ruby
lambda
return
proc-object
MetaFu
źródło
źródło
Odpowiedzi:
Po prostu użyj
next
w 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ą maszLocalJumpError
break
zwraca wartość z bloku i kończy jego wywołanie. Jeśli twój blok został wywołany przezyield
lub.call
, to równieżbreak
przerywa działanie tego iteratoranext
zwraca wartość z bloku i kończy jego wywołanie. Jeśli twój blok został wywołany przezyield
lub.call
, tonext
zwraca wartość do wiersza, w którymyield
został wywołanyźródło
Nie możesz tego zrobić w Rubim.
Słowo
return
kluczowe 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 # ...
źródło
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 }
źródło
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
źródło
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.
źródło
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,
yield
jeśli to wystarczy w Twoim scenariuszu).źródło
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 }
źródło