Ruby bloğunda 'return' kullanmak


89

Ruby 1.9.1'i gömülü bir betik dili için kullanmaya çalışıyorum, böylece "son kullanıcı" kodu bir Ruby bloğuna yazılır. Bununla ilgili bir sorun, kullanıcıların bloklarda 'return' anahtar kelimesini kullanabilmelerini istememdir, böylece örtük dönüş değerleri konusunda endişelenmelerine gerek kalmaz. Bunu aklımızda tutarak, yapmak istediğim şey bu:

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

thing {
  return 6 * 7
}

Yukarıdaki örnekte 'return' kullanırsam, bir LocalJumpError alıyorum. Bunun söz konusu bloğun lambda değil Proc olması nedeniyle olduğunun farkındayım. 'Return' i kaldırırsam kod çalışır, ancak bu senaryoda gerçekten 'return' kullanabilmeyi tercih ederim. Mümkün mü? Bloğu bir lambda'ya dönüştürmeyi denedim, ancak sonuç aynı.


Neden örtük bir dönüş değerinden kaçınmak istiyorsunuz?
marcgg

@marcgg - Burada ilgili bir sorum var - stackoverflow.com/questions/25953519/… .
sid smith

Yanıtlar:


173

Sadece nextbu bağlamda kullanın :

$ 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 her zaman yöntemden döner, ancak bu parçacığı irb'de test ederseniz bir yönteme sahip değilsiniz, bu yüzden LocalJumpError
  • breakbloktan değer döndürür ve çağrısını bitirir. Engellenenler tarafından çağrılan ise yieldveya .calldaha sonra breakbu İlerleticiden sonları çok
  • nextbloktan değer döndürür ve çağrısını bitirir. Bloğunuz yieldveya ile çağrıldıysa .call, nextdeğeri yieldçağrıldığı satıra döndürür

4
bir
işlemde

"bloktan sonraki değeri döndürür ve çağrıyı bitirir" den bu bilgiyi nereden aldığınızı belirtebilir misiniz? Daha fazlasını okumak istiyorum.
user566245

O oldu Ruby Programlama Dili yanlış hatırlamıyorsam kitapta (Ben şu anda el altında bu yok). Az önce google'ı kontrol ettim ve o kitaptan olduğuna inanıyorum: librairie.immateriel.fr/fr/read_book/9780596516178/… ve oradan sonraki 2 sayfa (bu benim içeriğim ve sayfalarım değil, sadece Google'da araştırdım). Ama gerçekten orijinal kitabı tavsiye ederim, çok daha fazla cevher açıklandı.
MBO

Ayrıca kafamdan cevap verdim, sadece irb'deki şeyleri kontrol ettim, bu yüzden cevabım teknik veya tam değil. Daha fazla bilgi için Ruby Programlama Dili kitabına bakın.
MBO

Keşke bu cevap en üstte olsaydı. Yeterince yükseltemiyorum.
btx9000

20

Bunu Ruby'de yapamazsınız.

returnAnahtar her zaman geçerli bağlamda yöntemi veya lambda döner. Bloklarda, kapatmanın yapıldığı yöntemden dönecektir. tanımlandığı . Çağıran yöntem veya lambda'dan dönmek yapılamaz .

Rubyspec bu Ruby için doğru davranış (kuşkusuz değil gerçek uygulama, ancak amaçları C Ruby ile tam uyumluluk) gerçekten de şunu göstermektedir:

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

Bir bloktan /
işlemden

3

Yanlış bir açıdan bakıyorsun. Bu bir thinglambda meselesi değil.

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

thing {
  6 * 7
}

1

Şey nerede çağrılır? Bir sınıfın içinde misin?

Bunun gibi bir şey kullanmayı düşünebilirsiniz:

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

Ruby'de bir web çerçevesi için DSL yazarken aynı sorunu yaşadım ... (Anorexic web çerçevesi harika olacak!) ...

her neyse, Ruby'nin iç kısımlarını araştırdım ve bir Proc çağrısı geri döndüğünde dönen LocalJumpError'ı kullanarak basit bir çözüm buldum ... Şimdiye kadar testlerde iyi çalışıyor, ancak tam olarak kanıtlandığından emin değilim:

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

kurtarma bölümündeki if ifadesi muhtemelen şöyle görünebilir:

if e.is_a? LocalJumpError

ama benim için keşfedilmemiş bir bölge, bu yüzden şimdiye kadar test ettiğim şeye bağlı kalacağım.


1

Dezavantajlara rağmen bunun doğru yanıt olduğuna inanıyorum:

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
}

Bu hack, kullanıcıların sonuçsuz olarak geri dönüşü kullanmalarına, kendini korumaya vb. İzin verir.

Burada Thread kullanmanın avantajı, bazı durumlarda LocalJumpError'ı alamayacağınız ve dönüşün en beklenmedik yerde (üst düzey bir yöntemin yanında, beklenmedik bir şekilde gövdesinin geri kalanını atlayarak) gerçekleşmesidir.

Ana dezavantaj, potansiyel ek yüktür ( yieldSenaryonuzda yeterliyse , Thread + birleşimini değiştirebilirsiniz ).


1

Bir yol buldum, ancak bir yöntemi ara adım olarak tanımlamayı içeriyor:

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

thing { return 6 * 7 }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.