Di Ruby 1.8, ada perbedaan halus antara proc / lambda di satu sisi, dan Proc.new
di sisi lain.
- Apa perbedaannya?
- Bisakah Anda memberikan panduan tentang cara memutuskan mana yang akan dipilih?
- Di Ruby 1.9, proc dan lambda berbeda. Apa masalahnya?
Di Ruby 1.8, ada perbedaan halus antara proc / lambda di satu sisi, dan Proc.new
di sisi lain.
Jawaban:
Perbedaan penting lainnya yang tidak kentara antara procs yang dibuat dengan lambda
dan procs yang dibuat Proc.new
adalah bagaimana mereka menangani return
pernyataan:
lambda
proc yang dibuat, return
pernyataan hanya mengembalikan dari proc itu sendiriProc.new
proc yang dibuat, return
pernyataan itu sedikit lebih mengejutkan: ia mengembalikan kontrol tidak hanya dari proc, tetapi juga dari metode yang melampirkan proc!Inilah lambda
-pro yang dibuat return
dalam aksi. Berperilaku dengan cara yang mungkin Anda harapkan:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
Sekarang inilah Proc.new
proc yang dibuat return
melakukan hal yang sama. Anda akan melihat salah satu kasus di mana Ruby melanggar Prinsip Least Surprise yang banyak dibanggakan:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
Berkat perilaku ini mengejutkan (serta kurang mengetik), saya cenderung untuk mendukung menggunakan lambda
lebih Proc.new
ketika membuat procs.
proc
metode. Apakah itu hanya singkatan Proc.new
?
proc
sama denganProc.new
proc
bertindak suka lambda
dan tidak suka Proc.new
sehubungan dengan pernyataan pengembalian. Itu berarti ruby doc tidak akurat.
proc
bertindak seperti lambda
di 1.8, tetapi bertindak seperti Proc.new
di 1.9. Lihat jawaban Peter Wagenet.
lambda
adalah metode anonim. Karena ini adalah metode, ia mengembalikan nilai, dan metode yang menyebutnya dapat melakukan apa pun yang diinginkannya, termasuk mengabaikannya dan mengembalikan nilai yang berbeda. A Proc
seperti menempelkan dalam cuplikan kode. Itu tidak bertindak seperti metode. Jadi ketika pengembalian terjadi di dalam Proc
, itu hanya bagian dari kode metode yang memanggilnya.
Untuk memberikan klarifikasi lebih lanjut:
Joey mengatakan bahwa perilaku pengembalian Proc.new
mengejutkan. Namun ketika Anda menganggap bahwa Proc.new berperilaku seperti blok, ini tidak mengherankan karena itulah persisnya perilaku blok. lambas di sisi lain berperilaku lebih seperti metode.
Ini sebenarnya menjelaskan mengapa Procs fleksibel ketika datang ke arity (jumlah argumen) sedangkan lambda tidak. Blok tidak memerlukan semua argumen mereka diberikan tetapi metode melakukannya (kecuali jika standar disediakan). Meskipun memberikan argumen lambda default bukanlah pilihan di Ruby 1.8, sekarang didukung di Ruby 1.9 dengan sintaks alternatif lambda (seperti yang dicatat oleh webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Dan Michiel de Mare (OP) salah tentang Procs dan lambda berperilaku sama dengan arity di Ruby 1.9. Saya telah memverifikasi bahwa mereka masih mempertahankan perilaku dari 1,8 seperti yang ditentukan di atas.
break
pernyataan sebenarnya tidak masuk akal baik dalam Procs atau lambdas. Dalam Procs, break akan mengembalikan Anda dari Proc.new yang telah selesai. Dan tidak masuk akal untuk melepaskan diri dari lambda karena pada dasarnya ini adalah metode, dan Anda tidak akan pernah melepaskan diri dari level teratas metode.
next
,, redo
dan raise
berperilaku sama dalam Procs dan lambdas. Padahal retry
tidak diperbolehkan di keduanya dan akan memunculkan eksepsi.
Dan akhirnya, proc
metode ini tidak boleh digunakan karena tidak konsisten dan memiliki perilaku yang tidak terduga. Di Ruby 1.8 sebenarnya mengembalikan lambda! Di Ruby 1.9 ini telah diperbaiki dan mengembalikan Proc. Jika Anda ingin membuat Proc, tetap dengan Proc.new
.
Untuk informasi lebih lanjut, saya sangat merekomendasikan O'Reilly The Ruby Programming Language yang merupakan sumber saya untuk sebagian besar informasi ini.
break
dari Procs menimbulkan LocalJumpError
, sedangkan break
dari lambdas berperilaku seperti return
( yaitu , return nil
).
Saya menemukan halaman ini yang menunjukkan apa perbedaan antara Proc.new
dan lambda
. Menurut halaman, satu-satunya perbedaan adalah bahwa lambda ketat tentang jumlah argumen yang diterimanya, sedangkan Proc.new
mengkonversi argumen yang hilang menjadi nil
. Berikut adalah contoh sesi IRB yang menggambarkan perbedaan:
irb (utama): 001: 0> l = lambda {| x, y | x + y} => # <Proc: 0x00007fc605ec0748 @ (irb): 1> irb (main): 002: 0> p = Proc.new {| x, y | x + y} => # <Proc: 0x00007fc605ea8698 @ (irb): 2> irb (utama): 003: 0> l.call "hello", "world" => "helloworld" irb (utama): 004: 0> p.call "hello", "world" => "helloworld" irb (utama): 005: 0> l.call "hello" ArgumentError: jumlah argumen yang salah (1 untuk 2) dari (irb): 1 from (irb): 5: in `call ' dari (irb): 5 dari: 0 irb (utama): 006: 0> p.call "hello" TypeError: tidak dapat mengonversi nil menjadi String dari (irb): 2: dalam `+ ' dari (irb): 2 from (irb): 6: in `call ' dari (irb): 6 dari: 0
Halaman ini juga merekomendasikan penggunaan lambda kecuali Anda secara spesifik menginginkan perilaku toleran kesalahan. Saya setuju dengan sentimen ini. Menggunakan lambda tampaknya sedikit lebih ringkas, dan dengan perbedaan yang tidak signifikan, tampaknya pilihan yang lebih baik dalam situasi rata-rata.
Adapun Ruby 1.9, maaf, saya belum melihat ke 1.9, tapi saya tidak membayangkan mereka akan banyak mengubahnya (jangan menuruti kata saya untuk itu, sepertinya Anda telah mendengar beberapa perubahan, jadi Saya mungkin salah di sana).
Proc lebih tua, tetapi semantik pengembalian sangat berlawanan dengan saya (setidaknya ketika saya belajar bahasa) karena:
Lambda secara fungsional lebih aman dan lebih mudah untuk dipikirkan - saya selalu menggunakannya daripada proc.
Saya tidak bisa bicara banyak tentang perbedaan yang halus. Namun, saya dapat menunjukkan bahwa Ruby 1.9 sekarang memungkinkan parameter opsional untuk lambdas dan blok.
Berikut sintaks baru untuk lambdas yang stabby di bawah 1.9:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8 tidak memiliki sintaks itu. Cara konvensional mendeklarasikan block / lambdas juga tidak mendukung argumen opsional:
# under 1.8
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
Namun, Ruby 1.9 mendukung argumen opsional bahkan dengan sintaks lama:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
Jika Anda ingin membangun Ruby1.9 untuk Leopard atau Linux, lihat artikel ini (promosi mandiri yang tidak tahu malu).
Jawaban singkat: Yang penting adalah apa yang return
dilakukan: lambda kembali dengan sendirinya, dan proc kembali dari dirinya sendiri DAN fungsi yang memanggilnya.
Yang kurang jelas adalah mengapa Anda ingin menggunakan masing-masing. lambda adalah apa yang kita harapkan harus dilakukan dalam pengertian pemrograman fungsional. Ini pada dasarnya merupakan metode anonim dengan lingkup saat ini secara otomatis terikat. Dari keduanya, lambda adalah yang mungkin harus Anda gunakan.
Proc, di sisi lain, sangat berguna untuk mengimplementasikan bahasa itu sendiri. Misalnya, Anda dapat menerapkan pernyataan "jika" atau "untuk" loop dengannya. Setiap pengembalian yang ditemukan dalam proc akan kembali dari metode yang memanggilnya, bukan hanya pernyataan "jika". Beginilah cara kerja bahasa, bagaimana pernyataan "jika" bekerja, jadi tebakan saya adalah Ruby menggunakan ini di balik selimut dan mereka hanya mengeksposnya karena sepertinya kuat.
Anda hanya benar-benar membutuhkan ini jika Anda membuat konstruksi bahasa baru seperti loop, konstruksi if-else, dll.
Cara yang baik untuk melihatnya adalah bahwa lambda dieksekusi dalam ruang lingkup mereka sendiri (seolah-olah itu adalah pemanggilan metode), sementara Procs dapat dilihat sebagai dieksekusi sesuai dengan metode pemanggilan, setidaknya itu adalah cara yang baik untuk memutuskan mana yang akan digunakan dalam setiap kasus.
Saya tidak melihat ada komentar pada metode ketiga dalam pencarian, "proc" yang sudah usang, tetapi ditangani secara berbeda dalam 1,8 dan 1,9.
Berikut adalah contoh yang cukup bertele-tele yang membuatnya mudah untuk melihat perbedaan antara tiga panggilan serupa:
def meth1
puts "method start"
pr = lambda { return }
pr.call
puts "method end"
end
def meth2
puts "method start"
pr = Proc.new { return }
pr.call
puts "method end"
end
def meth3
puts "method start"
pr = proc { return }
pr.call
puts "method end"
end
puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
proc
mengembalikan lambda dalam 1,8; sekarang telah diperbaiki untuk mengembalikan proc di 1.9 - namun ini merupakan perubahan besar; karenanya tidak direkomendasikan untuk digunakan lagi
Penutupan di Ruby adalah gambaran umum yang bagus untuk bagaimana blok, lambda dan proc bekerja di Ruby, dengan Ruby.
lambda berfungsi seperti yang diharapkan, seperti dalam bahasa lain.
Kabelnya Proc.new
mengejutkan dan membingungkan.
The return
pernyataan dalam proc dibuat oleh Proc.new
tidak hanya akan mengembalikan kontrol hanya dari dirinya sendiri, tetapi juga dari metode melampirkan itu .
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
Anda bisa membantah bahwa Proc.new
memasukkan kode ke dalam metode melampirkan, seperti halnya blok. Tetapi Proc.new
menciptakan objek, sedangkan blok adalah bagian dari objek.
Dan ada perbedaan lain antara lambda dan Proc.new
, yang merupakan penanganan argumen (salah) mereka. lambda mengeluh tentang itu, sementara Proc.new
mengabaikan argumen tambahan atau menganggap tidak adanya argumen sebagai nol.
irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):21:in `block in irb_binding'
from (irb):25:in `call'
from (irb):25
from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
from (irb):47:in `block in irb_binding'
from (irb):49:in `call'
from (irb):49
from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"
BTW, proc
di Ruby 1.8 menciptakan lambda, sementara di Ruby 1.9+ berperilaku seperti Proc.new
, yang benar-benar membingungkan.
Untuk menguraikan respons Accordion Guy:
Perhatikan bahwa Proc.new
membuat proc keluar dengan melewati blok. Saya percaya itu lambda {...}
diurai sebagai semacam literal, bukan pemanggilan metode yang melewati blok. return
ing dari dalam blok yang dilampirkan ke pemanggilan metode akan kembali dari metode, bukan blok, dan Proc.new
kasing adalah contoh dari ini yang dimainkan.
(Ini 1.8. Saya tidak tahu bagaimana ini diterjemahkan ke 1.9.)
Saya agak terlambat dalam hal ini, tetapi ada satu hal besar tetapi sedikit diketahui tentang Proc.new
tidak disebutkan dalam komentar sama sekali. Seperti dengan dokumentasi :
Proc::new
dapat dipanggil tanpa blok hanya dalam metode dengan blok terlampir, dalam hal ini blok tersebut dikonversi keProc
objek.
Yang mengatakan, Proc.new
mari untuk metode rantai menghasilkan:
def m1
yield 'Finally!' if block_given?
end
def m2
m1 &Proc.new
end
m2 { |e| puts e }
#⇒ Finally!
&block
argumen dalam def
, tetapi tanpa harus melakukannya dalam daftar argumen arg.
Perlu ditekankan bahwa return
dalam proc, kembali dari metode lexically enclosure, yaitu metode di mana proc dibuat , bukan metode yang disebut proc. Ini adalah konsekuensi dari properti penutupan procs. Jadi kode berikut tidak menghasilkan apa-apa:
def foo
proc = Proc.new{return}
foobar(proc)
puts 'foo'
end
def foobar(proc)
proc.call
puts 'foobar'
end
foo
Meskipun proc mengeksekusi foobar
, itu dibuat foo
dan return
keluar foo
, bukan hanya foobar
. Seperti yang ditulis Charles Caldwell di atas, ia memiliki perasaan GOTO. Menurut pendapat saya, return
baik-baik saja di blok yang dieksekusi dalam konteks leksikal, tetapi jauh kurang intuitif ketika digunakan dalam proc yang dieksekusi dalam konteks yang berbeda.
Perbedaan perilaku dengan return
IMHO perbedaan paling penting antara 2. Saya juga lebih suka lambda karena kurang mengetik daripada Proc.new :-)
proc {}
. Saya tidak yakin kapan ini mulai berlaku, tetapi (sedikit) lebih mudah daripada harus mengetikkan Proc.new.