Kapan harus menggunakan lambda, kapan harus menggunakan Proc.new?

336

Di Ruby 1.8, ada perbedaan halus antara proc / lambda di satu sisi, dan Proc.newdi 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?
Michiel de Mare
sumber
3
Lihat juga: buku Bahasa Pemrograman Ruby oleh Matz dan Flanagan, telah membahas topik ini secara komprehensif. proc berperilaku seperti semantik hasil blok, sedangkan lambda berperilaku seperti metode - metode memanggil semantik. Juga kembali, istirahat, dan sebagainya. semua berperilaku berbeda dalam procs n lambdas
Gishu
1
Lihat juga pos terperinci tentang perbedaan aliran Kontrol antara Ruby Procs dan Lambdas
Akshay Rawat
Anda telah menerima jawaban yang hanya mengatakan apa perbedaan antara proc dan lambda, sedangkan judul pertanyaan Anda adalah kapan harus menggunakan hal-hal itu
Shri

Jawaban:

378

Perbedaan penting lainnya yang tidak kentara antara procs yang dibuat dengan lambdadan procs yang dibuat Proc.newadalah bagaimana mereka menangani returnpernyataan:

  • Dalam lambdaproc yang dibuat, returnpernyataan hanya mengembalikan dari proc itu sendiri
  • Dalam Proc.newproc yang dibuat, returnpernyataan itu sedikit lebih mengejutkan: ia mengembalikan kontrol tidak hanya dari proc, tetapi juga dari metode yang melampirkan proc!

Inilah lambda-pro yang dibuat returndalam 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.newproc yang dibuat returnmelakukan 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 lambdalebih Proc.newketika membuat procs.

Joey deVilla
sumber
12
Lalu ada juga procmetode. Apakah itu hanya singkatan Proc.new?
panzi
6
@panzi, ya, procsama denganProc.new
ma11hew28
4
@mattdipasquale Dalam tes saya, procbertindak suka lambdadan tidak suka Proc.newsehubungan dengan pernyataan pengembalian. Itu berarti ruby ​​doc tidak akurat.
Kelvin
31
@mattdipasquale Maaf saya hanya setengah benar. procbertindak seperti lambdadi 1.8, tetapi bertindak seperti Proc.newdi 1.9. Lihat jawaban Peter Wagenet.
Kelvin
55
Mengapa perilaku "mengejutkan" ini? A lambdaadalah 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 Procseperti menempelkan dalam cuplikan kode. Itu tidak bertindak seperti metode. Jadi ketika pengembalian terjadi di dalam Proc, itu hanya bagian dari kode metode yang memanggilnya.
Arcolye
96

Untuk memberikan klarifikasi lebih lanjut:

Joey mengatakan bahwa perilaku pengembalian Proc.newmengejutkan. 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.

breakpernyataan 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,, redodan raiseberperilaku sama dalam Procs dan lambdas. Padahal retrytidak diperbolehkan di keduanya dan akan memunculkan eksepsi.

Dan akhirnya, procmetode 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.

Peter Wagenet
sumber
1
"" "Namun ketika Anda menganggap bahwa Proc.new berperilaku seperti sebuah blok, ini tidak mengherankan karena itulah persisnya perilaku balok." "" <- blok adalah bagian dari suatu objek, sementara Proc.new menciptakan sebuah objek. Baik lambda dan Proc.new menciptakan objek yang kelasnya adalah Proc, mengapa berbeda?
lemah
1
Pada Ruby 2.5, breakdari Procs menimbulkan LocalJumpError, sedangkan breakdari lambdas berperilaku seperti return( yaitu , return nil).
Masa Sakano
43

Saya menemukan halaman ini yang menunjukkan apa perbedaan antara Proc.newdan lambda. Menurut halaman, satu-satunya perbedaan adalah bahwa lambda ketat tentang jumlah argumen yang diterimanya, sedangkan Proc.newmengkonversi 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).

Mike Stone
sumber
2
procs juga kembali secara berbeda dari lambdas.
Cam
"" "Proc.new mengonversi argumen yang hilang menjadi" "" Proc.new juga mengabaikan argumen tambahan (tentu saja lambda mengeluh dengan kesalahan).
lemah
16

Proc lebih tua, tetapi semantik pengembalian sangat berlawanan dengan saya (setidaknya ketika saya belajar bahasa) karena:

  1. Jika Anda menggunakan proc, kemungkinan besar Anda menggunakan semacam paradigma fungsional.
  2. Proc dapat kembali keluar dari lingkup terlampir (lihat tanggapan sebelumnya), yang pada dasarnya merupakan kebohongan, dan sangat tidak fungsional.

Lambda secara fungsional lebih aman dan lebih mudah untuk dipikirkan - saya selalu menggunakannya daripada proc.

Charles Caldwell
sumber
11

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

webmat
sumber
Params opsional di dalam lambda sangat dibutuhkan, saya senang mereka menambahkannya di 1.9. Saya berasumsi blok juga dapat memiliki parameter opsional juga (dalam 1.9)?
mpd
Anda tidak mendemonstrasikan parameter default dalam blok, hanya lambdas
iconoclast
11

Jawaban singkat: Yang penting adalah apa yang returndilakukan: 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.

Evan Moran
sumber
1
"lambda kembali dari dirinya sendiri, dan proc kembali dari dirinya sendiri DAN fungsi yang menyebutnya" jelas salah dan kesalahpahaman yang sangat umum. Proc adalah penutupan dan pengembalian dari metode yang membuatnya. Lihat jawaban lengkap saya di bagian lain halaman ini.
ComDubh
10

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.

krusty.ar
sumber
8

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
Dave Rapin
sumber
1
Matz telah menyatakan bahwa dia berencana untuk mencabutnya karena membingungkan memiliki proc dan Proc. Baru mengembalikan hasil yang berbeda. Pada 1.9 mereka berperilaku sama (proc adalah alias untuk Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin
@banister: procmengembalikan lambda dalam 1,8; sekarang telah diperbaiki untuk mengembalikan proc di 1.9 - namun ini merupakan perubahan besar; karenanya tidak direkomendasikan untuk digunakan lagi
Gishu
Saya pikir beliung mengatakan dalam catatan kaki di suatu tempat bahwa proc secara efektif terdeparasi atau sesuatu. Saya tidak memiliki nomor halaman yang tepat.
dertoni
7

Penutupan di Ruby adalah gambaran umum yang bagus untuk bagaimana blok, lambda dan proc bekerja di Ruby, dengan Ruby.

swrobel
sumber
Saya berhenti membaca ini setelah saya membaca "suatu fungsi tidak dapat menerima banyak blok - melanggar prinsip bahwa penutupan dapat diedarkan secara bebas sebagai nilai." Blok bukan penutup. Procs adalah, dan suatu fungsi dapat menerima beberapa procs.
ComDubh
5

lambda berfungsi seperti yang diharapkan, seperti dalam bahasa lain.

Kabelnya Proc.newmengejutkan dan membingungkan.

The returnpernyataan dalam proc dibuat oleh Proc.newtidak 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.newmemasukkan kode ke dalam metode melampirkan, seperti halnya blok. Tetapi Proc.newmenciptakan 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.newmengabaikan 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, procdi Ruby 1.8 menciptakan lambda, sementara di Ruby 1.9+ berperilaku seperti Proc.new, yang benar-benar membingungkan.

lemah
sumber
3

Untuk menguraikan respons Accordion Guy:

Perhatikan bahwa Proc.newmembuat proc keluar dengan melewati blok. Saya percaya itu lambda {...}diurai sebagai semacam literal, bukan pemanggilan metode yang melewati blok. returning dari dalam blok yang dilampirkan ke pemanggilan metode akan kembali dari metode, bukan blok, dan Proc.newkasing adalah contoh dari ini yang dimainkan.

(Ini 1.8. Saya tidak tahu bagaimana ini diterjemahkan ke 1.9.)

Peeja
sumber
3

Saya agak terlambat dalam hal ini, tetapi ada satu hal besar tetapi sedikit diketahui tentang Proc.newtidak disebutkan dalam komentar sama sekali. Seperti dengan dokumentasi :

Proc::newdapat dipanggil tanpa blok hanya dalam metode dengan blok terlampir, dalam hal ini blok tersebut dikonversi keProc objek.

Yang mengatakan, Proc.newmari untuk metode rantai menghasilkan:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!
Aleksei Matiushkin
sumber
Menarik, ia melakukan hal yang sama dengan mendeklarasikan &blockargumen dalam def, tetapi tanpa harus melakukannya dalam daftar argumen arg.
jrochkind
2

Perlu ditekankan bahwa returndalam 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 foodan returnkeluar foo, bukan hanya foobar. Seperti yang ditulis Charles Caldwell di atas, ia memiliki perasaan GOTO. Menurut pendapat saya, returnbaik-baik saja di blok yang dieksekusi dalam konteks leksikal, tetapi jauh kurang intuitif ketika digunakan dalam proc yang dieksekusi dalam konteks yang berbeda.

ComDubh
sumber
1

Perbedaan perilaku dengan returnIMHO perbedaan paling penting antara 2. Saya juga lebih suka lambda karena kurang mengetik daripada Proc.new :-)

Orion Edwards
sumber
2
Untuk memperbarui: procs sekarang dapat dibuat menggunakan proc {}. Saya tidak yakin kapan ini mulai berlaku, tetapi (sedikit) lebih mudah daripada harus mengetikkan Proc.new.
aceofbassgreg