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.
ruby
lambda
return
proc-object
MetaFu
sumber
sumber
Jawaban:
Cukup gunakan
next
dalam 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 punyaLocalJumpError
break
mengembalikan nilai dari blok dan mengakhiri panggilannya. Jika blok Anda dipanggil olehyield
atau.call
, makabreak
putus dari iterator ini juganext
mengembalikan nilai dari blok dan mengakhiri panggilannya. Jika blok Anda dipanggil olehyield
atau.call
, kemudiannext
mengembalikan nilai ke baris tempatyield
dipanggilsumber
Anda tidak dapat melakukannya di Ruby.
Kata
return
kunci 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 # ...
sumber
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 }
sumber
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
sumber
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.
sumber
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
yield
jika itu cukup dalam skenario Anda).sumber
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 }
sumber