Masalah Menerapkan Penutupan dalam Pengaturan Non-fungsional

18

Dalam bahasa pemrograman, penutupan adalah fitur yang populer dan sering diinginkan. Wikipedia mengatakan (beri penekanan pada saya):

Dalam ilmu komputer, closure (...) adalah fungsi bersama-sama dengan lingkungan referensi untuk variabel non-lokal dari fungsi itu. Penutupan memungkinkan suatu fungsi untuk mengakses variabel di luar lingkup leksikal langsungnya.

Jadi penutupan pada dasarnya adalah nilai fungsi (anonim?) Yang dapat menggunakan variabel di luar cakupannya sendiri. Dalam pengalaman saya, ini berarti dapat mengakses variabel yang ada dalam ruang lingkup pada titik definisi.

Dalam praktiknya, konsepnya tampaknya menyimpang, setidaknya di luar pemrograman fungsional. Bahasa yang berbeda menerapkan semantik yang berbeda, bahkan tampaknya ada perang pendapat. Banyak programmer tampaknya tidak tahu apa penutupan itu, melihat mereka sebagai lebih dari fungsi anonim.

Juga, tampaknya ada hambatan besar ketika menerapkan penutupan. Paling menonjol, Java 7 seharusnya menyertakan mereka tetapi fitur itu didorong kembali ke rilis masa depan.

Mengapa penutupan begitu sulit (untuk dipahami dan) untuk direalisasikan? Ini adalah pertanyaan yang terlalu luas dan tidak jelas, jadi izinkan saya lebih memfokuskannya dengan pertanyaan yang saling terkait ini:

  • Apakah ada masalah dengan mengekspresikan penutupan dalam formalisme semantik yang umum (langkah kecil, langkah besar, ...)?
  • Apakah sistem tipe yang ada tidak cocok untuk penutupan dan tidak dapat diperpanjang dengan mudah?
  • Apakah bermasalah untuk membuat penutupan sejalan dengan terjemahan prosedur tradisional berbasis stack?

Perhatikan bahwa pertanyaan ini sebagian besar berkaitan dengan bahasa prosedural, berorientasi objek, dan skrip pada umumnya. Sejauh yang saya tahu, bahasa fungsional tidak memiliki masalah.

Raphael
sumber
Pertanyaan bagus. Penutupan telah diimplementasikan dalam Scala, dan Martin Odersky menulis kompiler Java 1.5, jadi tidak jelas mengapa mereka tidak ada di Java 7. C # memilikinya. (Saya akan mencoba menulis jawaban yang lebih baik nanti.)
Dave Clarke
4
Bahasa fungsional yang tidak murni seperti Lisp dan ML mengakomodasi penutupan dengan baik, jadi tidak mungkin ada alasan semantik intrinsik bagi mereka untuk bermasalah.
Gilles 'SO- stop being evil'
Saya memasukkan item tersebut karena saya telah berjuang untuk membayangkan seperti apa bentuk semantic langkah kecil untuk penutupan. Sangat mungkin bahwa penutupan dalam diri mereka sendiri tidak menjadi masalah tetapi termasuk mereka dalam bahasa yang tidak dirancang dengan mereka dalam pikiran itu sulit.
Raphael
1
Lihatlah pdfs.semanticscholar.org/73a2/… - Lua penulis membuatnya dengan sangat cerdik dan membahas masalah umum dalam mengimplementasikan penutupan juga
Bulat

Jawaban:

10

Bolehkah saya mengarahkan Anda ke halaman wikipedia masalah Funarg ? Setidaknya begitulah orang-orang penyusun digunakan untuk referensi masalah implementasi penutupan.

Jadi penutupan pada dasarnya adalah nilai fungsi (anonim?) Yang dapat menggunakan variabel di luar cakupannya sendiri. Dalam pengalaman saya, ini berarti dapat mengakses variabel yang ada dalam ruang lingkup pada titik definisi.

Sementara definisi ini masuk akal, itu tidak membantu menggambarkan masalah penerapan fungsi kelas satu dalam bahasa tradisional runtime-stack. Ketika datang ke masalah implementasi, fungsi kelas satu dapat secara kasar dibagi menjadi dua kelas:

  • Variabel lokal dalam fungsi tidak pernah digunakan setelah fungsi kembali.
  • Variabel lokal dapat digunakan setelah fungsi kembali.

Kasus pertama (ke bawah funargs) tidak terlalu sulit untuk diimplementasikan dan dapat ditemukan bahkan pada bahasa prosedural yang lebih lama, seperti Algol, C dan Pascal. C jenis rok masalah, karena tidak memungkinkan fungsi bersarang tetapi Algol dan Pascal melakukan pembukuan yang diperlukan untuk memungkinkan fungsi dalam referensi variabel stack dari fungsi luar.

Kasus kedua (ke atas funargs), di sisi lain, membutuhkan catatan aktivasi untuk disimpan di luar tumpukan, di heap. Ini berarti sangat mudah untuk membocorkan sumber daya memori kecuali bahasa runtime termasuk pengumpul sampah. Sementara hampir semuanya adalah sampah yang dikumpulkan hari ini, membutuhkan satu masih keputusan desain yang signifikan dan bahkan lebih dari beberapa waktu lalu.


Adapun contoh khusus Jawa, jika saya ingat dengan benar, masalah utama sebenarnya tidak bisa menerapkan penutupan, tetapi bagaimana memperkenalkan mereka ke bahasa dengan cara yang tidak berlebihan dengan fitur yang ada (seperti kelas dalam anonim) dan yang tidak berbenturan dengan fitur yang ada (seperti pengecualian yang diperiksa - masalah yang bukan hal sepele yang harus saya pecahkan dan yang kebanyakan orang tidak pikirkan pada awalnya).

Saya juga dapat memikirkan hal-hal lain yang membuat fungsi kelas pertama kurang sepele untuk diimplementasikan, seperti memutuskan apa yang harus dilakukan dengan variabel "ajaib" seperti ini , mandiri atau super dan bagaimana berinteraksi dengan operator aliran kontrol yang ada, seperti istirahat dan kembali (apakah kami ingin mengizinkan pengembalian non-lokal atau tidak?). Tetapi pada akhirnya, popularitas fungsi kelas satu baru-baru ini tampaknya menunjukkan bahwa bahasa yang tidak memilikinya sebagian besar melakukannya karena alasan historis atau karena beberapa keputusan desain yang signifikan sejak awal.

hugomg
sumber
1
Apakah Anda tahu ada bahasa yang membedakan huruf besar dan kecil? Dalam bahasa .NET, metode generik yang diharapkan menerima fungsi ke bawah saja dapat menerima struktur tipe generik bersama dengan delegasi yang akan menerima struktur seperti byref (dalam C #, " refparameter"). Jika penelepon merangkum semua variabel yang menarik dalam struktur, delegasi bisa sepenuhnya statis, menghindari kebutuhan untuk alokasi tumpukan. Compiler tidak menawarkan bantuan sintaks yang bagus untuk konstruksi seperti itu, tetapi Kerangka dapat mendukung mereka.
supercat
2
@supercat: Rust memiliki beberapa tipe penutupan yang memungkinkan Anda menerapkan pada waktu kompilasi jika fungsi bagian dalam perlu menggunakan heap. Namun, ini tidak berarti implementasi tidak dapat mencoba untuk menghindari alokasi tumpukan tanpa memaksa Anda untuk peduli dengan semua jenis tambahan itu. Kompiler dapat mencoba menyimpulkan fungsi masa hidup atau dapat menggunakan pemeriksaan runtime untuk menyimpan variabel ke tumpukan dengan malas hanya ketika benar-benar diperlukan (lihat bagian "lingkup leksikal" dari kertas Evolution Lua untuk detailnya)
hugomg
5

Kita bisa melihat bagaimana penutupan diimplementasikan dalam C #. Skala transformasi yang dilakukan oleh kompiler C # memperjelas bahwa cara mereka menerapkan penutupan cukup banyak pekerjaan. Mungkin ada cara yang lebih mudah untuk menerapkan penutupan, tapi saya pikir tim C # compiler akan mengetahui hal ini.

Pertimbangkan pseudo-C # berikut (saya hentikan beberapa hal spesifik C #):

int x = 1;
function f = function() { x++; };
for (int i = 1; i < 10; i++) {
    f();
}
print x; // Should print 9

Kompiler mengubah ini menjadi sesuatu seperti ini:

class FunctionStuff {
   int x;
   void theFunction() {
       x++;
   }
}

FunctionStuff theClosureObject = new FunctionStuff();
theClosureObject.x = 1;
for (int i = 1; i < 10; i++) {
    theClosureObject.theFunction();
}
print theClosureObject.x; // Should print 9

(pada kenyataannya, variabel f ​​masih akan dibuat, di mana f adalah 'delegate' (= function pointer), tetapi delegate ini masih terkait dengan objek theClosureObject - saya meninggalkan bagian ini untuk kejelasan bagi mereka yang tidak terbiasa dengan C #)

Transformasi ini cukup besar dan rumit: pertimbangkan penutupan di dalam penutupan dan interaksi penutupan dengan sisa fitur bahasa C #. Saya bisa membayangkan bahwa fitur itu didorong kembali untuk Java, karena Java 7 sudah memiliki cukup banyak fitur baru.

Alex ten Brink
sumber
Saya bisa melihat ke mana arahnya; memiliki banyak penutupan dan akses ruang lingkup utama variabel yang sama akan berantakan.
Raphael
Sejujurnya, ini lebih karena menggunakan kerangka kerja OO yang ada untuk menerapkan penutupan kemudian masalah nyata dengan mereka. Bahasa lain hanya mengalokasikan variabel dalam struktur terpisah, metode-kurang, dan kemudian membiarkan beberapa penutupan berbagi jika mereka mau.
hugomg
@ Raphael: bagaimana perasaan Anda tentang penutupan di dalam penutupan? Tunggu sebentar, izinkan saya menambahkan itu.
Alex ten Brink
5

Untuk menjawab bagian dari pertanyaan Anda. Formalisme yang dijelaskan oleh Morrisett dan Harper mencakup semantik langkah besar dan kecil dari bahasa polimorfik tingkat tinggi yang mengandung penutup. Ada makalah sebelum ini yang menyediakan jenis semantik yang Anda cari. Lihat, misalnya, di mesin SECD . Menambahkan referensi yang bisa berubah atau lokal yang bisa berubah ke semantik ini sangatlah mudah. Saya tidak melihat ada masalah teknis dalam menyediakan semantik seperti itu.

Dave Clarke
sumber
Terima kasih untuk referensi! Tampaknya tidak cocok untuk membaca ringan, tapi itu mungkin diharapkan dari makalah semantik.
Raphael
1
@ Raphael: Mungkin ada yang lebih sederhana di sekitar. Saya akan mencoba menemukan sesuatu dan kembali kepada Anda. Bagaimanapun, Gambar 8 memiliki semantik yang Anda cari.
Dave Clarke
Mungkin Anda bisa memberikan tanggapan ikhtisar kasar. ide sentral dalam jawaban Anda?
Raphael
2
@Raphael. Mungkin saya bisa merujuk Anda ke catatan kuliah saya yang saya gunakan untuk kursus bahasa pemrograman, yang memberi Anda pengantar cepat. Silakan lihat selebaran 8 dan 9.
Uday Reddy
1
Tautan itu tampak mati atau di belakang otentikasi tidak terlihat. ( cs.cmu.edu/afs/cs/user/rwh/public/www/home/papers/gcpoly/tr.pdf ). Saya mendapatkan 403 terlarang.
Ben Fletcher