Menggunakan 'return' di blok Ruby

89

Saya mencoba menggunakan Ruby 1.9.1 untuk bahasa skrip yang disematkan, sehingga kode "pengguna akhir" ditulis dalam blok Ruby. Satu masalah dengan ini adalah saya ingin pengguna dapat menggunakan kata kunci 'kembali' di blok, jadi mereka tidak perlu khawatir tentang nilai pengembalian implisit. Dengan pemikiran ini, inilah hal yang ingin saya lakukan:

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

thing {
  return 6 * 7
}

Jika saya menggunakan 'return' dalam contoh di atas, saya mendapatkan LocalJumpError. Saya sadar bahwa ini karena blok yang dimaksud adalah Proc dan bukan lambda. Kode berfungsi jika saya menghapus 'kembali', tetapi saya benar-benar lebih suka dapat menggunakan 'kembali' dalam skenario ini. Apakah ini mungkin? Saya sudah mencoba mengonversi blok ke lambda, tetapi hasilnya sama.

MetaFu
sumber
mengapa Anda ingin menghindari nilai pengembalian implisit?
marcgg
@marcgg - Saya punya pertanyaan terkait di sini - stackoverflow.com/questions/25953519/… .
sid smith

Jawaban:

173

Cukup gunakan nextdalam konteks ini:

$ 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 selalu kembali dari metode, tetapi jika Anda menguji potongan ini di irb Anda tidak memiliki metode, itulah mengapa Anda punya LocalJumpError
  • breakmengembalikan nilai dari blok dan mengakhiri panggilannya. Jika blok Anda dipanggil oleh yieldatau .call, maka breakputus dari iterator ini juga
  • nextmengembalikan nilai dari blok dan mengakhiri panggilannya. Jika blok Anda dipanggil oleh yieldatau .call, kemudian nextmengembalikan nilai ke baris tempat yielddipanggil
MBO
sumber
4
istirahat di proc akan memunculkan pengecualian
gfreezy
dapatkah Anda mengutip dari mana Anda mendapatkan informasi ini dari "nilai pengembalian berikutnya dari blok dan mengakhiri panggilannya". Saya ingin membaca lebih lanjut tentang itu.
pengguna566245
Itu dari buku Bahasa Pemrograman Ruby (saya tidak memilikinya sekarang) jika saya ingat dengan benar. Saya baru saja memeriksa google dan saya yakin ini dari buku itu: librairie.immateriel.fr/fr/read_book/9780596516178/… dan 2 halaman berikutnyax dari sana (bukan konten saya dan halaman saya, saya hanya mencarinya di Google). Tapi saya sangat merekomendasikan buku asli, itu menjelaskan lebih banyak permata.
MBO
Juga saya jawab dari kepala saya, hanya mengecek di irb, itu sebabnya jawaban saya tidak teknis atau lengkap. Untuk informasi lebih lanjut, periksa buku Ruby Programming Language.
MBO
Saya berharap jawaban ini ada di atas. Saya tidak bisa cukup memberikan suara positif.
btx9000
20

Anda tidak dapat melakukannya di Ruby.

Kata returnkunci selalu kembali dari metode atau lambda dalam konteks saat ini. Dalam blok, itu akan kembali dari metode di mana closure didefinisikan . Itu tidak dapat dibuat untuk kembali dari metode pemanggilan atau lambda.

The Rubyspec menunjukkan bahwa ini memang perilaku yang benar untuk Ruby (diakui bukan implementasi nyata, tetapi tujuan kompatibilitas penuh dengan 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
sumber
Ada artikel rinci tentang kembali dari blok / proc di sini
ComDubh
3

Anda melihatnya dari sudut pandang yang salah. Ini adalah masalah thing, bukan lambda.

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

thing {
  6 * 7
}
Simone Carletti
sumber
1

Dimana hal itu dipanggil? Apakah Anda berada di dalam kelas?

Anda dapat mempertimbangkan untuk menggunakan sesuatu seperti ini:

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
sumber
1

Saya memiliki masalah yang sama saat menulis DSL untuk kerangka web di ruby ​​... (kerangka web Anorexic akan bergoyang!) ...

bagaimanapun, saya menggali ke dalam ruby ​​internal dan menemukan solusi sederhana menggunakan LocalJumpError kembali ketika panggilan Proc kembali ... itu berjalan dengan baik dalam tes sejauh ini, tapi saya tidak yakin itu bukti penuh:

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

pernyataan if di segmen penyelamatan mungkin terlihat seperti ini:

if e.is_a? LocalJumpError

tapi itu wilayah yang belum dipetakan bagi saya, jadi saya akan tetap berpegang pada apa yang saya uji sejauh ini.

Myst
sumber
1

Saya yakin ini adalah jawaban yang benar, terlepas dari kekurangannya:

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
}

Peretasan ini memungkinkan pengguna untuk menggunakan pengembalian dalam proses mereka tanpa konsekuensi, disimpan sendiri, dll.

Keuntungan menggunakan Thread di sini adalah bahwa dalam beberapa kasus Anda tidak akan mendapatkan LocalJumpError - dan pengembalian akan terjadi di tempat yang paling tidak terduga (di dalam metode tingkat atas, secara tidak terduga melewatkan bagian tubuhnya yang lain).

Kerugian utama adalah overhead potensial (Anda dapat mengganti Thread + join dengan hanya yieldjika itu cukup dalam skenario Anda).

Cezary Baginski
sumber
1

Saya menemukan cara, tetapi ini melibatkan pendefinisian metode sebagai langkah perantara:

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

thing { return 6 * 7 }
s12chung.dll
sumber