Apa perbedaan antara 'penutupan' dan 'lambda'?

811

Bisakah seseorang menjelaskan? Saya mengerti konsep dasar di belakangnya tetapi saya sering melihat mereka digunakan secara bergantian dan saya menjadi bingung.

Dan sekarang kita di sini, bagaimana mereka berbeda dari fungsi biasa?

pemain ski
sumber
85
Lambdas adalah konstruksi bahasa (fungsi anonim), penutupan adalah teknik implementasi untuk mengimplementasikan fungsi kelas satu (apakah anonim atau tidak). Sayangnya, ini sering membingungkan banyak orang.
Andreas Rossberg
Untuk penutupan PHP, lihat php.net/manual/en/class.closure.php . Bukan apa yang diharapkan oleh programmer JavaScript.
PaulH
Jawaban SasQ sangat bagus. IMHO pertanyaan ini akan lebih bermanfaat bagi pengguna SO jika membimbing pemirsa ke jawaban itu.
AmigoNico

Jawaban:

703

Sebuah lambda hanya fungsi anonim - fungsi didefinisikan tanpa nama. Dalam beberapa bahasa, seperti Skema, mereka setara dengan fungsi yang disebutkan. Bahkan, definisi fungsi ditulis ulang sebagai pengikatan lambda ke variabel secara internal. Dalam bahasa lain, seperti Python, ada beberapa (agak tidak perlu) perbedaan di antara mereka, tetapi mereka berperilaku dengan cara yang sama sebaliknya.

Sebuah penutupan adalah fungsi yang menutup lebih dalam lingkungan di mana ia didefinisikan. Ini berarti dapat mengakses variabel yang tidak ada dalam daftar parameternya. Contoh:

def func(): return h
def anotherfunc(h):
   return func()

Ini akan menyebabkan kesalahan, karena functidak menutup lingkungan di anotherfunc- htidak terdefinisi. funchanya menutup lingkungan global. Ini akan berhasil:

def anotherfunc(h):
    def func(): return h
    return func()

Karena di sini, funcdidefinisikan dalam anotherfunc, dan dalam python 2.3 dan lebih besar (atau angka seperti ini) ketika mereka hampir mendapatkan penutupan yang benar (mutasi masih tidak berfungsi), ini berarti bahwa ia menutup anotherfunc lingkungan dan dapat mengakses variabel di dalam Itu. Dalam Python 3.1 +, mutasi bekerja juga ketika menggunakan yang nonlocalkata kunci .

Poin penting lain - funcakan terus menutupi anotherfunclingkungan 's bahkan ketika itu tidak lagi sedang dievaluasi dalam anotherfunc. Kode ini juga akan berfungsi:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

Ini akan mencetak 10.

Ini, seperti yang Anda perhatikan, tidak ada hubungannya dengan lambda s - mereka adalah dua konsep yang berbeda (meskipun terkait).

Claudiu
sumber
1
Claudiu, setahu saya python pengetahuan tidak pernah benar-benar mendapatkan penutupan yang benar. Apakah mereka memperbaiki masalah mutabilitas sementara saya tidak melihat? Sangat mungkin ...
simon
simon - Anda benar, mereka masih belum sepenuhnya benar. Saya pikir python 3.0 akan menambahkan hack untuk mengatasi ini. (sesuatu seperti pengidentifikasi 'nonlokal'). Saya akan mengubah jawaban saya untuk mencerminkan itu.
Claudiu
2
@AlexanderOrlov: Keduanya adalah lambdas dan penutup. Java memiliki penutupan sebelumnya melalui kelas batin anonim. Sekarang fungsionalitas menjadi lebih mudah secara sintaksis melalui ekspresi lambda. Jadi mungkin aspek yang paling relevan dari fitur baru adalah bahwa sekarang ada lambda. Tidak salah menyebut mereka lambda, mereka memang lambda. Mengapa penulis Java 8 dapat memilih untuk tidak menyoroti fakta bahwa mereka adalah penutupan bukan sesuatu yang saya tahu.
Claudiu
2
@AlexanderOrlov karena Java 8 lambdas bukanlah penutupan yang sebenarnya, itu adalah simulasi penutupan. Mereka lebih mirip dengan penutupan Python 2.3 (tidak ada mutabilitas, maka persyaratan untuk variabel yang dirujuk sebagai 'efektif final'), dan secara internal mengkompilasi ke fungsi non-penutupan yang mengambil semua variabel yang direferensikan dalam lingkup melampirkan sebagai parameter tersembunyi.
Logan Pickup
7
@Claudiu Saya pikir referensi untuk implementasi bahasa tertentu (Python) dapat terlalu memperumit jawabannya. Pertanyaannya sepenuhnya agnostik bahasa (dan juga tidak memiliki tag khusus bahasa).
Matius
319

Ada banyak kebingungan di sekitar lambdas dan penutupan, bahkan dalam jawaban atas pertanyaan StackOverflow ini di sini. Alih-alih bertanya kepada programmer secara acak yang belajar tentang penutupan dari praktik dengan bahasa pemrograman tertentu atau programmer yang tidak mengerti, lakukan perjalanan ke sumbernya (di mana semuanya dimulai). Dan karena lambda dan penutupan berasal dari Lambda Calculus yang ditemukan oleh Gereja Alonzo di tahun 30-an sebelum komputer elektronik pertama ada, ini adalah sumber yang saya bicarakan.

Lambda Calculus adalah bahasa pemrograman paling sederhana di dunia. Satu-satunya hal yang dapat Anda lakukan di dalamnya: ►

  • APLIKASI: Menerapkan satu ekspresi ke yang lain, dilambangkan f x.
    (Anggap saja sebagai panggilan fungsi , di mana ffungsi dan xsatu-satunya parameter)
  • ABSTRAKSI: Mengikat simbol yang terjadi dalam ekspresi untuk menandai bahwa simbol ini hanya "slot", kotak kosong menunggu untuk diisi dengan nilai, "variabel" seolah-olah. Hal ini dilakukan dengan menambahkan huruf Yunani λ(lambda), lalu nama simbolik (misalnya x), lalu satu titik .sebelum ekspresi. Ini kemudian mengubah ekspresi menjadi fungsi yang mengharapkan satu parameter .
    Misalnya: λx.x+2mengambil ekspresi x+2dan memberi tahu bahwa simbol xdalam ekspresi ini adalah variabel terikat - ia dapat diganti dengan nilai yang Anda berikan sebagai parameter.
    Perhatikan bahwa fungsi yang didefinisikan dengan cara ini adalah anonim- itu tidak memiliki nama, sehingga Anda tidak bisa menyebutnya, tapi Anda dapat segera menghubungi itu (ingat aplikasi?) Dengan memasok parameter itu sedang menunggu, seperti ini: (λx.x+2) 7. Maka ungkapan (dalam hal ini nilai literal) 7diganti seperti xdalam subekspresi x+2lambda yang diterapkan, sehingga Anda dapatkan 7+2, yang kemudian dikurangi 9dengan aturan aritmatika yang umum.

Jadi kami telah memecahkan salah satu misteri:
lambda adalah fungsi anonim dari contoh di atas λx.x+2,.


Dalam bahasa pemrograman yang berbeda, sintaks untuk abstraksi fungsional (lambda) mungkin berbeda. Misalnya, dalam JavaScript tampilannya seperti ini:

function(x) { return x+2; }

dan Anda dapat langsung menerapkannya ke beberapa parameter seperti ini:

(function(x) { return x+2; })(7)

atau Anda dapat menyimpan fungsi anonim ini (lambda) ke dalam beberapa variabel:

var f = function(x) { return x+2; }

yang secara efektif memberinya nama f, memungkinkan Anda untuk merujuknya dan menyebutnya beberapa kali kemudian, misalnya:

alert(  f(7) + f(10)  );   // should print 21 in the message box

Tetapi Anda tidak harus menyebutkannya. Anda dapat segera menyebutnya:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

Di LISP, lambdas dibuat seperti ini:

(lambda (x) (+ x 2))

dan Anda dapat memanggil lambda dengan menerapkannya langsung ke parameter:

(  (lambda (x) (+ x 2))  7  )


OK, sekarang saatnya untuk memecahkan misteri lain: apa itu penutupan . Untuk melakukan itu, mari kita bicara tentang simbol ( variabel ) dalam ekspresi lambda.

Seperti yang saya katakan, apa yang dilakukan abstraksi lambda adalah mengikat simbol dalam subekspresi, sehingga menjadi parameter yang dapat diganti . Simbol semacam itu disebut terikat . Tetapi bagaimana jika ada simbol lain dalam ekspresi? Sebagai contoh: λx.x/y+2. Dalam ungkapan ini, simbol xdiikat oleh abstraksi lambda yang λx.mendahuluinya. Tapi simbol lainnya,, ytidak terikat - itu gratis . Kita tidak tahu apa itu dan dari mana asalnya, jadi kita tidak tahu apa artinya dan nilai apa yang diwakilinya, dan oleh karena itu kita tidak dapat mengevaluasi ungkapan itu sampai kita mencari tahu apa yartinya.

Bahkan, hal yang sama berlaku dengan dua simbol lainnya, 2dan +. Hanya saja kita begitu akrab dengan dua simbol ini sehingga kita biasanya lupa bahwa komputer tidak mengenal mereka dan kita perlu mengatakan apa artinya dengan mendefinisikannya di suatu tempat, misalnya di perpustakaan atau bahasa itu sendiri.

Anda dapat memikirkan simbol bebas seperti yang didefinisikan di tempat lain, di luar ekspresi, dalam "konteks sekitarnya", yang disebut lingkungannya . Lingkungan mungkin merupakan ekspresi yang lebih besar bahwa ungkapan ini adalah bagian dari (seperti yang dikatakan Qui-Gon Jinn: "Selalu ada ikan yang lebih besar";)), atau di perpustakaan, atau dalam bahasa itu sendiri (sebagai primitif ).

Ini memungkinkan kami membagi ekspresi lambda menjadi dua kategori:

  • Ekspresi TERTUTUP: setiap simbol yang muncul dalam ekspresi ini terikat oleh abstraksi lambda. Dengan kata lain, mereka mandiri ; mereka tidak memerlukan konteks sekitarnya untuk dievaluasi. Mereka juga disebut combinator .
  • Ekspresi BUKA: beberapa simbol dalam ekspresi ini tidak terikat - yaitu, beberapa simbol yang terjadi di dalamnya adalah gratis dan mereka memerlukan beberapa informasi eksternal, dan dengan demikian mereka tidak dapat dievaluasi sampai Anda memberikan definisi simbol-simbol ini.

Anda dapat TUTUP ekspresi lambda terbuka dengan memasok lingkungan , yang mendefinisikan semua simbol bebas ini dengan mengikatnya ke beberapa nilai (yang mungkin berupa angka, string, fungsi anonim alias lambda, apa pun ...).

Dan di sini datang penutupan bagian:
The penutupan dari ekspresi lambda adalah set tertentu dari simbol-simbol yang didefinisikan dalam konteks luar (lingkungan) yang memberikan nilai ke simbol bebas dalam ekspresi ini, membuat mereka tidak bebas lagi. Itu mengubah ekspresi lambda terbuka , yang masih mengandung beberapa simbol gratis "tidak terdefinisi", menjadi yang tertutup , yang tidak memiliki simbol gratis lagi.

Misalnya, jika Anda memiliki ekspresi lambda berikut:, λx.x/y+2simbol xterikat, sedangkan simbol ybebas, maka ekspresi itu opendan tidak dapat dievaluasi kecuali jika Anda mengatakan apa yartinya (dan sama dengan +dan 2, yang juga gratis). Tapi anggaplah Anda juga memiliki lingkungan seperti ini:

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

Ini lingkungan pasokan definisi untuk semua "tidak terdefinisi" (bebas) simbol dari ekspresi lambda kami ( y, +, 2), dan beberapa simbol tambahan ( q, w). Simbol yang perlu kita definisikan adalah bagian dari lingkungan ini:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

dan inilah tepatnya penutupan ekspresi lambda kami:>

Dengan kata lain, itu menutup ekspresi lambda terbuka. Di sinilah nama penutup berasal dari tempat pertama, dan inilah mengapa jawaban banyak orang di utas ini tidak sepenuhnya benar: P


Jadi mengapa mereka salah? Mengapa begitu banyak dari mereka mengatakan bahwa penutupan adalah beberapa struktur data dalam memori, atau beberapa fitur dari bahasa yang mereka gunakan, atau mengapa mereka mengacaukan penutupan dengan lambda? : P

Nah, marketoid korporat dari Sun / Oracle, Microsoft, Google dll yang harus disalahkan, karena itulah yang mereka sebut konstruk ini dalam bahasa mereka (Java, C #, Go, dll.). Mereka sering menyebut "penutupan" apa yang seharusnya hanya lambda. Atau mereka menyebut "penutupan" teknik tertentu yang mereka gunakan untuk menerapkan pelingkupan leksikal, yaitu fakta bahwa suatu fungsi dapat mengakses variabel yang didefinisikan dalam lingkup luarnya pada saat definisi. Mereka sering mengatakan bahwa fungsi "membungkus" variabel-variabel ini, yaitu, menangkap mereka ke dalam beberapa struktur data untuk menyelamatkan mereka dari kehancuran setelah fungsi luar selesai dijalankan. Tapi ini hanyalah post-factum "etimologi cerita rakyat" dan pemasaran, yang hanya membuat segalanya lebih membingungkan,

Dan itu bahkan lebih buruk karena fakta bahwa selalu ada sedikit kebenaran dalam apa yang mereka katakan, yang tidak memungkinkan Anda untuk dengan mudah mengabaikannya sebagai salah: P Izinkan saya menjelaskan:

Jika Anda ingin menerapkan bahasa yang menggunakan lambdas sebagai warga negara kelas satu, Anda perlu mengizinkan mereka untuk menggunakan simbol yang didefinisikan dalam konteks sekitarnya (yaitu, untuk menggunakan variabel gratis di lambda Anda). Dan simbol-simbol ini harus ada di sana bahkan ketika fungsi sekitarnya kembali. Masalahnya adalah bahwa simbol-simbol ini terikat ke beberapa penyimpanan lokal dari fungsi (biasanya di tumpukan panggilan), yang tidak akan ada lagi ketika fungsi kembali. Oleh karena itu, agar lambda bekerja seperti yang Anda harapkan, Anda perlu "menangkap" semua variabel bebas dari konteks luarnya dan menyimpannya nanti, bahkan ketika konteks luarnya hilang. Artinya, Anda perlu menemukan penutupannyadari lambda Anda (semua variabel eksternal yang digunakan) dan menyimpannya di tempat lain (baik dengan membuat salinan, atau dengan menyiapkan ruang untuknya di muka, di tempat lain selain di tumpukan). Metode aktual yang Anda gunakan untuk mencapai tujuan ini adalah "detail implementasi" bahasa Anda. Yang penting di sini adalah penutupan , yang merupakan set variabel bebas dari lingkungan lambda Anda yang perlu disimpan di suatu tempat.

Tidak terlalu lama bagi orang untuk mulai memanggil struktur data aktual yang mereka gunakan dalam implementasi bahasa mereka untuk mengimplementasikan penutupan sebagai "penutupan" itu sendiri. Strukturnya biasanya terlihat seperti ini:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

dan struktur data ini sedang diedarkan sebagai parameter ke fungsi lain, dikembalikan dari fungsi, dan disimpan dalam variabel, untuk mewakili lambda, dan memungkinkan mereka untuk mengakses lingkungan terlampir mereka serta kode mesin untuk berjalan dalam konteks itu. Tapi itu hanya cara (salah satu dari banyak) untuk melaksanakan penutupan, bukan yang penutupan itu sendiri.

Seperti yang saya jelaskan di atas, penutupan ekspresi lambda adalah bagian dari definisi di lingkungannya yang memberikan nilai kepada variabel bebas yang terkandung dalam ekspresi lambda itu, yang secara efektif menutup ekspresi (mengubah ekspresi lambda terbuka , yang tidak dapat dievaluasi lagi, menjadi a tertutup lambda ekspresi, yang kemudian dapat dievaluasi, karena semua simbol-simbol yang terkandung di dalamnya sekarang didefinisikan).

Yang lainnya hanyalah "kultus kargo" dan "sihir voo-doo" dari para programmer dan vendor bahasa yang tidak mengetahui akar sebenarnya dari gagasan ini.

Saya harap itu menjawab pertanyaan Anda. Tetapi jika Anda memiliki pertanyaan tindak lanjut, jangan ragu untuk menanyakannya di komentar, dan saya akan mencoba menjelaskannya dengan lebih baik.

SasQ
sumber
50
Jawaban terbaik menjelaskan hal-hal umum daripada khusus bahasa
Shishir Arora
39
Saya suka pendekatan semacam ini ketika menjelaskan sesuatu. Mulai dari awal, menjelaskan cara kerja dan kemudian bagaimana kesalahpahaman saat ini dibuat. Jawaban ini perlu menuju ke atas.
Sharky
1
@SQQ> penutupan ekspresi lambda adalah himpunan bagian dari definisi di lingkungannya yang memberikan nilai pada variabel bebas yang terkandung dalam ekspresi lambda <Jadi dalam struktur data contoh Anda, apakah itu berarti lebih tepat untuk mengatakan "pointer ke lingkungan fungsi lambda "adalah penutupan?
Jeff M
3
Meskipun kalkulus Lambda terasa seperti bahasa mesin bagi saya, saya harus setuju bahwa itu adalah bahasa "ditemukan" berbeda dengan bahasa "buatan". Dan dengan demikian jauh lebih sedikit tunduk pada konvensi yang sewenang-wenang, dan jauh lebih cocok untuk menangkap struktur realitas yang mendasarinya. Kita dapat menemukan spesifik di Linq, JavaScript, F # lebih mudah didekati / diakses, tetapi kalkulus Lambda sampai ke inti masalah tanpa gangguan.
StevePoling
1
Saya menghargai bahwa Anda mengulangi poin Anda beberapa kali, dengan kalimat yang sedikit berbeda setiap kali. Ini membantu memperkuat konsep. Saya berharap lebih banyak orang melakukan ini.
johnklawlor
175

Ketika kebanyakan orang memikirkan fungsi , mereka berpikir tentang fungsi-fungsi bernama :

function foo() { return "This string is returned from the 'foo' function"; }

Ini disebut dengan nama, tentu saja:

foo(); //returns the string above

Dengan ekspresi lambda , Anda dapat memiliki fungsi anonim :

 @foo = lambda() {return "This is returned from a function without a name";}

Dengan contoh di atas, Anda dapat memanggil lambda melalui variabel yang ditugaskan untuknya:

foo();

Akan tetapi, lebih berguna daripada menugaskan fungsi anonim ke variabel, meneruskannya ke atau dari fungsi tingkat tinggi, yaitu fungsi yang menerima / mengembalikan fungsi lainnya. Dalam banyak kasus ini, penamaan fungsi tidak perlu:

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

Sebuah penutupan dapat bernama atau fungsi anonim, tetapi dikenal seperti ketika "menutup lebih" variabel dalam lingkup di mana fungsi didefinisikan, yaitu, penutupan masih akan mengacu pada lingkungan dengan variabel luar setiap yang digunakan dalam penutupan sendiri. Berikut ini adalah penutupan bernama:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

Itu tidak tampak seperti banyak tetapi bagaimana jika ini semua di fungsi lain dan Anda beralih incrementXke fungsi eksternal?

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

Ini adalah bagaimana Anda mendapatkan objek stateful dalam pemrograman fungsional. Karena penamaan "incrementX" tidak diperlukan, Anda dapat menggunakan lambda dalam hal ini:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }
Mark Cidade
sumber
14
bahasa apa yang kamu pakai di sini?
Claudiu
7
Ini pada dasarnya pseudocode. Ada beberapa lisp dan JavaScript di dalamnya, serta bahasa yang saya rancang disebut "@" ("at"), dinamai setelah operator deklarasi variabel.
Mark Cidade
3
@MarkCidade, jadi di mana bahasa ini @? Apakah ada dokumentasi dan donwload?
Pacerier
5
Mengapa tidak mengambil Javascript dan menambahkan batasan mendeklarasikan variabel dengan memimpin @ tanda? Itu akan menghemat waktu sedikit :)
Nemoden
5
@Pacerier: Saya mulai menerapkan bahasa: github.com/marxidad/At2015
Mark Cidade
54

Tidak semua penutupan adalah lambda dan tidak semua lambda adalah penutupan. Keduanya adalah fungsi, tetapi tidak harus dengan cara yang biasa kita ketahui.

Lambda pada dasarnya adalah fungsi yang didefinisikan inline daripada metode standar untuk mendeklarasikan fungsi. Lambdas sering dapat dilewatkan sebagai objek.

Penutupan adalah fungsi yang membungkus keadaan sekitarnya dengan mereferensikan bidang di luar tubuhnya. Keadaan terlampir tetap melintasi permintaan penutupan.

Dalam bahasa berorientasi objek, penutupan biasanya disediakan melalui objek. Namun, beberapa bahasa OO (misalnya C #) menerapkan fungsi khusus yang lebih dekat dengan definisi penutupan yang disediakan oleh bahasa yang murni fungsional (seperti lisp) yang tidak memiliki objek untuk menyertakan keadaan.

Apa yang menarik adalah bahwa pengenalan Lambdas dan Penutupan dalam C # membawa pemrograman fungsional lebih dekat dengan penggunaan umum.

Michael Brown
sumber
Jadi, dapatkah kita mengatakan bahwa penutupan adalah bagian dari lambda dan lambda adalah bagian dari fungsi?
pemain ski
Penutupan adalah bagian dari lambda ... tetapi lambda lebih istimewa daripada fungsi normal. Seperti yang saya katakan, lambdas didefinisikan inline. Pada dasarnya tidak ada cara untuk referensi mereka kecuali mereka diteruskan ke fungsi lain atau dikembalikan sebagai nilai balik.
Michael Brown
19
Lambdas dan closure masing-masing merupakan subset dari semua fungsi, tetapi hanya ada persimpangan antara lambdas dan closure, di mana bagian non-berpotongan dari closure akan dinamai fungsi yang merupakan closure dan lamdas yang tidak berpotongan adalah fungsi mandiri dengan sepenuhnya variabel terikat.
Mark Cidade
Menurut saya, lambda adalah konsep yang lebih mendasar daripada fungsi. Itu benar-benar tergantung pada bahasa pemrograman.
Wei Qiu
15

Ini sesederhana ini: lambda adalah konstruksi bahasa, yaitu hanya sintaks untuk fungsi anonim; closure adalah teknik untuk mengimplementasikannya - atau fungsi kelas satu, dalam hal ini, dinamai atau anonim.

Lebih tepatnya, penutupan adalah bagaimana fungsi kelas satu diwakili saat runtime, sebagai pasangan "kode" dan lingkungan "penutupan" atas semua variabel non-lokal yang digunakan dalam kode itu. Dengan cara ini, variabel-variabel tersebut masih dapat diakses bahkan ketika cakupan luar tempat mereka berasal telah keluar.

Sayangnya, ada banyak bahasa di luar sana yang tidak mendukung fungsi sebagai nilai kelas satu, atau hanya mendukungnya dalam bentuk lumpuh. Jadi orang sering menggunakan istilah "penutupan" untuk membedakan "yang asli".

Andreas Rossberg
sumber
12

Dari pandangan bahasa pemrograman, mereka benar-benar dua hal yang berbeda.

Pada dasarnya untuk bahasa lengkap Turing, kita hanya perlu elemen yang sangat terbatas, misalnya abstraksi, aplikasi, dan reduksi. Abstraksi dan aplikasi menyediakan cara Anda dapat membangun ekspresi lamdba, dan pengurangan menghilangkan makna ekspresi lambda.

Lambda menyediakan cara Anda untuk abstrak proses komputasi keluar. misalnya, untuk menghitung jumlah dua angka, suatu proses yang mengambil dua parameter x, y dan mengembalikan x + y dapat diabstraksikan. Dalam skema, Anda dapat menuliskannya sebagai

(lambda (x y) (+ x y))

Anda bisa mengganti nama parameter, tetapi tugas yang diselesaikannya tidak berubah. Di hampir semua bahasa pemrograman, Anda dapat memberikan ekspresi lambda nama, yang dinamai fungsi. Tetapi tidak ada banyak perbedaan, mereka secara konseptual dapat dianggap sebagai hanya sintaksis gula.

OK, sekarang bayangkan bagaimana ini bisa diterapkan. Setiap kali kita menerapkan ekspresi lambda ke beberapa ekspresi, mis

((lambda (x y) (+ x y)) 2 3)

Kita cukup mengganti parameter dengan ekspresi yang akan dievaluasi. Model ini sudah sangat kuat. Tetapi model ini tidak memungkinkan kita untuk mengubah nilai simbol, misalnya Kita tidak bisa meniru perubahan status. Jadi kita membutuhkan model yang lebih kompleks. Untuk membuatnya singkat, setiap kali kita ingin menghitung arti dari ekspresi lambda, kita menempatkan pasangan simbol dan nilai yang sesuai ke dalam suatu lingkungan (atau tabel). Kemudian sisanya (+ xy) dievaluasi dengan mencari simbol yang sesuai dalam tabel. Sekarang jika kami menyediakan beberapa primitif untuk beroperasi pada lingkungan secara langsung, kami dapat memodelkan perubahan status!

Dengan latar belakang ini, periksa fungsi ini:

(lambda (x y) (+ x y z))

Kita tahu bahwa ketika kita mengevaluasi ekspresi lambda, xy akan diikat dalam tabel baru. Tapi bagaimana dan di mana kita bisa mencari z? Sebenarnya z disebut variabel bebas. Harus ada lingkungan luar yang berisi z. Kalau tidak, makna ungkapan tidak dapat ditentukan hanya dengan mengikat x dan y. Untuk memperjelas ini, Anda dapat menulis sesuatu sebagai berikut dalam skema:

((lambda (z) (lambda (x y) (+ x y z))) 1)

Jadi z akan terikat ke 1 di tabel luar. Kami masih mendapatkan fungsi yang menerima dua parameter tetapi makna sebenarnya juga tergantung pada lingkungan luar. Dengan kata lain lingkungan luar ditutup pada variabel bebas. Dengan bantuan set !, kita dapat membuat fungsi stateful, yaitu, itu bukan fungsi dalam arti matematika. Apa yang dikembalikan tidak hanya bergantung pada input, tetapi juga z.

Ini adalah sesuatu yang sudah Anda ketahui dengan baik, metode objek hampir selalu bergantung pada keadaan objek. Itu sebabnya beberapa orang mengatakan "penutupan adalah objek orang miskin." Tapi kita juga bisa menganggap objek sebagai penutupan orang miskin karena kita benar-benar menyukai fungsi kelas satu.

Saya menggunakan skema untuk menggambarkan ide-ide karena skema itu adalah salah satu bahasa paling awal yang memiliki penutupan nyata. Semua materi di sini disajikan jauh lebih baik di SICP bab 3.

Singkatnya, lambda dan penutupan adalah konsep yang sangat berbeda. Lambda adalah fungsi. Penutupan adalah sepasang lambda dan lingkungan terkait yang menutup lambda.

Wei Qiu
sumber
Jadi kita bisa mengganti semua penutupan dengan lambdas bersarang sampai tidak ada variabel gratis lagi? Dalam hal ini saya akan mengatakan bahwa penutupan dapat dilihat sebagai jenis khusus lambda.
Trilarion
8

Konsepnya sama dengan yang dijelaskan di atas, tetapi jika Anda berasal dari latar belakang PHP, ini dijelaskan lebih lanjut menggunakan kode PHP.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

function ($ v) {return $ v> 2; } adalah definisi fungsi lambda. Kami bahkan dapat menyimpannya dalam variabel, sehingga dapat digunakan kembali:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

Sekarang, bagaimana jika Anda ingin mengubah angka maksimum yang diizinkan dalam array yang difilter? Anda harus menulis fungsi lambda lain atau membuat closure (PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

Penutupan adalah fungsi yang dievaluasi di lingkungannya sendiri, yang memiliki satu atau lebih variabel terikat yang dapat diakses ketika fungsi dipanggil. Mereka datang dari dunia pemrograman fungsional, di mana ada sejumlah konsep dalam permainan. Penutupan seperti fungsi lambda, tetapi lebih cerdas dalam arti bahwa mereka memiliki kemampuan untuk berinteraksi dengan variabel-variabel dari lingkungan luar di mana penutupan didefinisikan.

Berikut adalah contoh sederhana penutupan PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Dijelaskan dengan baik di artikel ini.

Pengembang
sumber
7

Pertanyaan ini sudah tua dan mendapat banyak jawaban.
Sekarang dengan Java 8 dan Lambda Resmi yang merupakan proyek penutupan tidak resmi, itu menghidupkan kembali pertanyaan.

Jawabannya dalam konteks Java (via Lambdas dan closures - apa bedanya? ):

"Penutupan adalah ekspresi lambda yang dipasangkan dengan lingkungan yang mengikat masing-masing variabel bebasnya dengan suatu nilai. Di Jawa, ekspresi lambda akan dilaksanakan dengan cara penutupan, sehingga kedua istilah tersebut digunakan secara bergantian dalam komunitas."

philippe lhardy
sumber
Bagaimana Lamdas diimplementasikan dengan penutupan di Jawa? Apakah itu berarti ekspresi Lamdas dikonversi ke kelas anonim gaya lama?
hackjutsu
5

Sederhananya, penutupan adalah trik tentang ruang lingkup, lambda adalah fungsi anonim. Kita dapat menyadari penutupan dengan lambda lebih elegan dan lambda sering digunakan sebagai parameter yang diteruskan ke fungsi yang lebih tinggi

feilengcui008
sumber
4

Ekspresi Lambda hanyalah fungsi anonim. di java polos, misalnya, Anda dapat menulis seperti ini:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

di mana Fungsi kelas hanya dibangun dalam kode java. Sekarang Anda dapat menelepon mapPersonToJob.apply(person)ke suatu tempat untuk menggunakannya. itu hanya satu contoh. Itu lambda sebelum ada sintaks untuk itu. Lambdas jalan pintas untuk ini.

Penutupan:

a Lambda menjadi penutup ketika dapat mengakses variabel di luar lingkup ini. Saya kira Anda bisa mengatakan sihirnya, secara ajaib dapat membungkus lingkungan tempat ia dibuat dan menggunakan variabel di luar ruang lingkupnya (lingkup luar. jadi jelas, sebuah penutup berarti lambda dapat mengakses RUANG LINGKUP LUAR.

di Kotlin, lambda selalu dapat mengakses penutupannya (variabel-variabel yang ada di lingkup luarnya)

j2emanue
sumber
0

Itu tergantung pada apakah suatu fungsi menggunakan variabel eksternal atau tidak untuk melakukan operasi.

Variabel eksternal - variabel yang didefinisikan di luar lingkup fungsi.

  • Ekspresi Lambda adalah stateless karena Itu tergantung pada parameter, variabel internal atau konstanta untuk melakukan operasi.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Penutupan memegang keadaan karena menggunakan variabel eksternal (yaitu variabel yang didefinisikan di luar ruang lingkup fungsi fungsi) bersama dengan parameter dan konstanta untuk melakukan operasi.

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Ketika Java membuat closure, ia membuat variabel n dengan fungsi sehingga bisa direferensikan ketika diteruskan ke fungsi lain atau digunakan di mana saja.

DIPANSHU GOYAL
sumber