Mengapa suatu program menggunakan penutupan?

58

Setelah membaca banyak posting yang menjelaskan tentang penutupan di sini, saya masih kehilangan konsep utama: Mengapa menulis sebuah penutupan? Apa tugas spesifik yang akan dilakukan oleh seorang programmer yang mungkin paling baik dilayani oleh penutupan?

Contoh penutupan di Swift adalah akses NSUrl dan menggunakan geocoder terbalik. Inilah salah satu contohnya. Sayangnya, kursus-kursus tersebut hanya menyajikan penutupan; mereka tidak menjelaskan mengapa solusi kode ditulis sebagai penutupan.

Contoh masalah pemrograman dunia nyata yang mungkin memicu otak saya untuk mengatakan, "aha, saya harus menulis penutup untuk ini", akan lebih informatif daripada diskusi teoretis. Tidak ada kekurangan diskusi teoretis yang tersedia di situs ini.

Bendrix
sumber
7
"Penjelasan baru-baru ini tentang Penutupan di sini" - apakah Anda melewatkan tautan?
Dan Pichelman
2
Anda harus meletakkan klarifikasi tentang tugas asinkron dalam komentar, di bawah jawaban yang relevan. Itu bukan bagian dari pertanyaan awal Anda, dan penjawabnya tidak akan pernah diberitahu tentang hasil edit Anda.
Robert Harvey
5
Hanya berita gembira: Titik penting dari Closures adalah pelingkupan leksikal (sebagai lawan pelingkupan dinamis), penutupan tanpa pelingkupan leksikal agak tidak berguna. Penutupan kadang-kadang disebut Penutup Lexical.
Hoffmann
1
Penutupan memungkinkan Anda untuk instantiate fungsi .
user253751
1
Baca buku bagus tentang pemrograman fungsional. Mungkin mulai dengan SICP
Basile Starynkevitch

Jawaban:

33

Pertama-tama, tidak ada yang mustahil tanpa menggunakan penutupan. Anda selalu dapat mengganti penutupan dengan objek yang mengimplementasikan antarmuka tertentu. Ini hanya masalah keringkasan dan pengurangan kopling.

Kedua, perlu diingat bahwa penutupan sering digunakan secara tidak tepat, di mana referensi fungsi sederhana atau konstruksi lainnya akan lebih jelas. Anda tidak harus mengambil setiap contoh yang Anda lihat sebagai praktik terbaik.

Di mana penutupan benar-benar bersinar di atas konstruksi lain adalah ketika menggunakan fungsi tingkat tinggi, ketika Anda benar - benar perlu berkomunikasi keadaan, dan Anda dapat menjadikannya satu garis, seperti dalam contoh JavaScript ini dari halaman wikipedia pada penutupan :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Di sini, thresholdsangat singkat dan alami dikomunikasikan dari mana ia didefinisikan ke tempat itu digunakan. Lingkupnya dibatasi sekecil mungkin. filtertidak harus ditulis untuk memungkinkan kemungkinan melewati data yang ditentukan klien seperti ambang batas. Kami tidak harus mendefinisikan struktur perantara apa pun untuk tujuan tunggal mengkomunikasikan ambang batas dalam fungsi kecil ini. Ini sepenuhnya mandiri.

Anda dapat menulis ini tanpa penutup, tetapi akan membutuhkan lebih banyak kode, dan lebih sulit untuk diikuti. Juga, JavaScript memiliki sintaks lambda yang cukup verbose. Dalam Scala, misalnya, seluruh fungsi tubuh akan menjadi:

bookList filter (_.sales >= threshold)

Namun, jika Anda dapat menggunakan ECMAScript 6 , berkat fungsi panah gemuk bahkan kode JavaScript menjadi lebih sederhana dan dapat benar-benar diletakkan pada satu baris.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

Dalam kode Anda sendiri, cari tempat-tempat di mana Anda menghasilkan banyak boilerplate hanya untuk mengkomunikasikan nilai sementara dari satu tempat ke tempat lain. Ini adalah peluang bagus untuk dipertimbangkan untuk diganti dengan penutupan.

Karl Bielefeldt
sumber
Bagaimana tepatnya penutupan mengurangi kopling?
sbichenko
Tanpa penutupan, Anda harus menerapkan batasan pada bestSellingBookskode dan filterkode, seperti antarmuka spesifik atau argumen data pengguna, agar dapat mengkomunikasikan thresholddata. Itu mengikat kedua fungsi bersama-sama dengan cara yang tidak dapat digunakan kembali.
Karl Bielefeldt
51

Sebagai penjelasan, saya akan meminjam beberapa kode dari posting blog yang luar biasa ini tentang penutupan . Ini JavaScript, tapi itulah bahasa yang paling banyak digunakan dalam posting blog yang berbicara tentang penutupan, karena penutupan sangat penting dalam JavaScript.

Katakanlah Anda ingin merender array sebagai tabel HTML. Anda bisa melakukannya seperti ini:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Tetapi Anda berada di bawah kekuasaan JavaScript tentang bagaimana setiap elemen dalam array akan ditampilkan. Jika Anda ingin mengontrol rendering, Anda bisa melakukan ini:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Dan sekarang Anda bisa melewatkan fungsi yang mengembalikan rendering yang Anda inginkan.

Bagaimana jika Anda ingin menampilkan total berjalan di setiap Baris Tabel? Anda membutuhkan variabel untuk melacak total itu, bukan? Penutupan memungkinkan Anda untuk menulis fungsi penyaji yang menutup variabel total yang berjalan, dan memungkinkan Anda untuk menulis penyaji yang dapat melacak total yang berjalan:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

Keajaiban yang terjadi di sini adalah bahwa renderInt mempertahankan akses ke totalvariabel, meskipun renderIntberulang kali dipanggil dan keluar.

Dalam bahasa berorientasi objek yang lebih tradisional daripada JavaScript, Anda bisa menulis kelas yang berisi variabel total ini, dan meneruskannya di sekitar alih-alih membuat penutupan. Tetapi penutupan adalah cara yang jauh lebih kuat, bersih dan elegan untuk melakukannya.

Bacaan lebih lanjut

Robert Harvey
sumber
10
Secara umum, Anda dapat mengatakan bahwa "fungsi kelas = = objek dengan hanya satu metode", "Penutupan == objek dengan hanya satu metode dan menyatakan", "objek == bundel penutupan".
Jörg W Mittag
Kelihatan bagus. Hal lain, dan ini mungkin teliti dan cermat, adalah bahwa Javascript adalah object-oriented, dan Anda bisa membuat objek yang berisi variabel total dan lulus bahwa sekitar. Penutupan tetap jauh lebih kuat, bersih, dan elegan, dan juga membuat Javascript jauh lebih idiomatis, tetapi cara penulisannya mungkin menyiratkan bahwa Javascript tidak dapat melakukan ini dengan cara yang berorientasi objek.
KRyan
2
Sebenarnya, menjadi benar - benar hebat : penutupan adalah yang membuat JavaScript berorientasi objek sejak awal! OO adalah tentang abstraksi data, dan penutupan adalah cara untuk melakukan abstraksi data dalam JavaScript.
Jörg W Mittag
@ JörgWMittag: Seperti Anda dapat melihat cloures sebagai objek dengan hanya satu metode, Anda dapat melihat objek sebagai kumpulan penutupan yang menutup variabel yang sama: variabel anggota suatu objek hanyalah variabel lokal dari konstruktor objek, dan metode objek adalah penutup yang ditentukan di dalam lingkup konstruktor dan tersedia untuk dipanggil nanti. Fungsi konstruktor mengembalikan fungsi orde tinggi (objek) yang dapat mengirimkan pada setiap penutupan sesuai dengan nama metode yang digunakan untuk doa.
Giorgio
@Giorgio: Memang, itulah bagaimana objek biasanya diimplementasikan dalam Skema, misalnya, dan sebenarnya juga bagaimana objek diimplementasikan dalam JavaScript (tidak mengherankan mengingat hubungannya yang dekat dengan Skema, setelah semua, Brendan Eich awalnya disewa untuk merancang sebuah Skema dialek dan mengimplementasikan juru bahasa Skema tertanam di dalam Netscape Navigator, dan hanya kemudian diperintahkan untuk membuat bahasa "dengan objek yang terlihat seperti C ++" setelah itu ia membuat jumlah minimum perubahan mutlak untuk memenuhi persyaratan pemasaran tersebut).
Jörg W Mittag
23

Tujuannya closureshanyalah untuk mempertahankan negara; maka nama closure- itu menutup negara. Untuk memudahkan penjelasan lebih lanjut, saya akan menggunakan Javascript.

Biasanya Anda memiliki fungsi

function sayHello(){
    var txt="Hello";
    return txt;
}

di mana ruang lingkup variabel terikat pada fungsi ini. Jadi setelah eksekusi, variabel txtkeluar dari cakupan. Tidak ada cara mengakses atau menggunakannya setelah fungsi selesai dieksekusi.

Penutupan adalah konstruk bahasa, yang memungkinkan - seperti yang dikatakan sebelumnya - untuk mempertahankan keadaan variabel dan memperpanjang ruang lingkup.

Ini bisa bermanfaat dalam berbagai kasus. Satu use case adalah pembangunan fungsi tingkat tinggi .

Dalam matematika dan ilmu komputer, fungsi tingkat tinggi (juga bentuk fungsional, fungsional atau functor) adalah fungsi yang melakukan setidaknya satu dari yang berikut: 1

  • mengambil satu atau lebih fungsi sebagai input
  • menghasilkan suatu fungsi

Contoh sederhana, tapi yang tidak terlalu berguna adalah:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

Anda mendefinisikan suatu fungsi makedadder, yang mengambil satu parameter sebagai input dan mengembalikan fungsi . Ada fungsi luarfunction(a){} dan batin function(b){}{} . Selanjutnya Anda mendefinisikan (secara implisit) fungsi lain add5sebagai hasil dari memanggil funtion orde tinggi makeadder. makeadder(5)mengembalikan fungsi anonim ( dalam ), yang pada gilirannya mengambil 1 parameter dan mengembalikan jumlah parameter fungsi luar dan parameter fungsi dalam .

The trick adalah, bahwa sementara mengembalikan batin fungsi, yang tidak sebenarnya menambahkan, ruang lingkup parameter dari fungsi luar ( a) dipertahankan. add5 ingat , bahwa parameternya aadalah 5.

Atau untuk menunjukkan setidaknya contoh yang berguna:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Penggunaan umum lainnya adalah apa yang disebut IIFE = ekspresi fungsi yang segera dipanggil. Sangat umum dalam javascript untuk memalsukan variabel anggota pribadi. Ini dilakukan melalui fungsi, yang menciptakan ruang lingkup pribadi = closure, karena langsung setelah definisi dipanggil. Strukturnya adalah function(){}(). Perhatikan tanda kurung ()setelah definisi. Ini memungkinkan untuk menggunakannya untuk pembuatan objek dengan pola modul yang terbuka . Triknya adalah membuat ruang lingkup dan mengembalikan objek, yang memiliki akses ke ruang lingkup ini setelah eksekusi IIFE.

Contoh Addi terlihat seperti ini:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

Objek yang dikembalikan memiliki referensi ke fungsi (misalnya publicSetName), yang pada gilirannya memiliki akses ke variabel "pribadi" privateVar.

Tapi ini kasus penggunaan yang lebih khusus untuk Javascript.

Apa tugas spesifik yang akan dilakukan oleh seorang programmer yang mungkin paling baik dilayani oleh penutupan?

Ada beberapa alasan untuk itu. Seseorang mungkin, bahwa itu wajar baginya, karena ia mengikuti paradigma fungsional . Atau dalam Javascript: itu hanya keharusan untuk mengandalkan penutupan untuk menghindari beberapa kebiasaan bahasa.

Thomas Junk
sumber
"Tujuan penutupan adalah semata-mata untuk mempertahankan keadaan; oleh karena itu penutupan nama - itu menutup negara.": Itu tergantung pada bahasa, sebenarnya. Penutupan menutup nama / variabel eksternal. Ini mungkin menunjukkan status (lokasi memori yang dapat diubah) tetapi juga dapat menunjukkan nilai (tidak berubah). Penutupan di Haskell tidak mempertahankan keadaan: mereka menyimpan informasi yang diketahui saat ini dan dalam konteks di mana penutupan dibuat.
Giorgio
1
»Mereka menyimpan informasi yang diketahui saat ini dan dalam konteks di mana penutupan itu dibuat« secara independen apakah itu dapat diubah atau tidak, itu adalah negara - mungkin negara yang tidak dapat diubah .
Thomas Junk
16

Ada dua kasus penggunaan utama untuk penutupan:

  1. Sinkronisasi. Katakanlah Anda ingin melakukan tugas yang akan memakan waktu cukup lama, lalu lakukan sesuatu saat itu selesai. Anda dapat membuat kode Anda menunggu hingga selesai, yang memblokir eksekusi lebih lanjut dan dapat membuat program Anda tidak responsif, atau memanggil tugas Anda secara tidak sinkron dan mengatakan "mulailah tugas panjang ini di latar belakang, dan ketika selesai, jalankan penutupan ini", di mana penutupan berisi kode untuk dieksekusi ketika selesai.

  2. Telepon balik. Ini juga dikenal sebagai "delegasi" atau "penangan acara" tergantung pada bahasa dan platform. Idenya adalah bahwa Anda memiliki objek yang dapat disesuaikan yang, pada titik-titik tertentu yang terdefinisi dengan baik, akan mengeksekusi suatu peristiwa , yang menjalankan penutupan yang dilewati oleh kode yang mengaturnya. Misalnya, di UI program Anda, Anda mungkin memiliki tombol, dan Anda memberikannya penutupan yang menahan kode untuk dieksekusi ketika pengguna mengklik tombol.

Ada beberapa kegunaan lain untuk penutupan, tetapi itu adalah dua yang utama.

Mason Wheeler
sumber
23
Jadi, pada dasarnya callback, karena contoh pertama juga merupakan callback.
Robert Harvey
2
@RobertHarvey: Secara teknis benar, tetapi mereka adalah model mental yang berbeda. Misalnya, (secara umum,) Anda mengharapkan event handler dipanggil beberapa kali, tetapi kelanjutan async Anda hanya dipanggil satu kali. Tapi ya, secara teknis apa pun yang akan Anda lakukan dengan penutupan adalah panggilan balik. (Kecuali jika Anda mengubahnya menjadi pohon ekspresi, tapi itu masalah yang sama sekali berbeda.);)
Mason Wheeler
4
@RobertHarvey: Melihat penutupan sebagai panggilan balik menempatkan Anda dalam kerangka pikiran yang benar-benar akan menghentikan Anda untuk menggunakannya secara efektif. Penutupan adalah callback pada steroid.
gnasher729
13
@MasonWheeler Begini, kedengarannya salah. Callback tidak ada hubungannya dengan penutupan; tentu saja Anda bisa memanggil kembali fungsi dalam penutupan atau memanggil penutupan sebagai panggilan balik; tetapi panggilan balik tidak perlu penutupan. Panggilan balik hanyalah panggilan fungsi sederhana. Pengurus acara tidak hanya merupakan penutupan. Delegasi tidak perlu penutupan: itu terutama C # cara pointer fungsi. Tentu saja Anda bisa menggunakannya untuk membangun penutupan. Penjelasan Anda tidak tepat. Intinya adalah menutup negara dan memanfaatkannya.
Thomas Junk
5
Mungkin ada konotasi khusus JS yang terjadi di sini yang membuat saya salah paham, tetapi jawaban ini terdengar sangat salah bagi saya. Sinkronisasi atau panggilan balik dapat menggunakan apa pun yang 'dapat dipanggil'. Penutupan tidak diperlukan untuk keduanya - fungsi reguler akan baik-baik saja. Sementara itu, penutupan, seperti yang didefinisikan dalam pemrograman fungsional, atau seperti yang digunakan dalam Python, berguna, seperti kata Thomas, karena itu 'menutup' beberapa keadaan, yaitu variabel, dan memberikan fungsi dalam ruang lingkup dalam akses yang konsisten ke variabel itu. nilai bahkan jika fungsi dipanggil dan keluar berkali-kali.
Jonathan Hartley
13

Beberapa contoh lain:

Penyortiran
Sebagian besar fungsi penyortiran beroperasi dengan membandingkan pasangan objek. Diperlukan beberapa teknik perbandingan. Membatasi perbandingan dengan operator tertentu berarti jenis yang agak tidak fleksibel. Pendekatan yang jauh lebih baik adalah menerima fungsi perbandingan sebagai argumen ke fungsi sortir. Kadang-kadang fungsi perbandingan stateless berfungsi dengan baik (misalnya, menyortir daftar angka atau nama), tetapi bagaimana jika perbandingan membutuhkan status?

Misalnya, pertimbangkan mengurutkan daftar kota berdasarkan jarak ke beberapa lokasi tertentu. Solusi jelek adalah menyimpan koordinat lokasi itu dalam variabel global. Ini membuat fungsi perbandingan itu sendiri tanpa kewarganegaraan, tetapi dengan biaya variabel global.

Pendekatan ini mencegah memiliki beberapa utas secara bersamaan mengurutkan daftar kota yang sama berdasarkan jarak mereka ke dua lokasi yang berbeda. Penutupan yang melingkupi lokasi memecahkan masalah ini, dan itu menghilangkan kebutuhan untuk variabel global.


Angka acak
Naskah asli rand()tidak mengambil argumen. Generator nomor pseudorandom membutuhkan status. Beberapa (mis., Mersenne Twister) membutuhkan banyak negara. Bahkan keadaan yang sederhana tapi mengerikan rand()membutuhkan. Baca makalah jurnal matematika pada generator angka acak baru dan Anda pasti akan melihat variabel global. Itu bagus untuk pengembang teknik, tidak begitu baik untuk penelepon. Meringkas keadaan itu dalam suatu struktur dan meneruskan struktur ke generator angka acak adalah salah satu cara mengatasi masalah data global. Ini adalah pendekatan yang digunakan dalam banyak bahasa non-OO untuk membuat reentrant generator angka acak. Penutupan menyembunyikan status itu dari penelepon. Penutupan menawarkan urutan panggilan sederhana rand()dan reentrancy dari negara yang dienkapsulasi.

Ada lebih banyak angka acak daripada hanya PRNG. Kebanyakan orang yang menginginkan keacakan ingin didistribusikan dengan cara tertentu. Saya akan mulai dengan angka yang diambil secara acak dari antara 0 dan 1, atau U (0,1) singkatnya. PRNG apa pun yang menghasilkan bilangan bulat antara 0 dan beberapa maksimum akan dilakukan; cukup bagi (sebagai titik apung) bilangan bulat acak dengan maksimum. Cara mudah dan umum untuk menerapkan ini adalah dengan membuat penutupan yang mengambil penutupan (PRNG) dan maksimum sebagai input. Sekarang kami memiliki generator acak generik dan mudah digunakan untuk U (0,1).

Ada sejumlah distribusi lain selain U (0,1). Misalnya, distribusi normal dengan mean dan standar deviasi tertentu. Setiap algoritma generator distribusi normal yang saya jalankan menggunakan generator U (0,1). Cara mudah dan generik untuk membuat generator normal adalah dengan membuat penutupan yang merangkum generator U (0,1), mean, dan standar deviasi sebagai keadaan. Ini, setidaknya secara konseptual, penutupan yang mengambil penutupan yang mengambil penutupan sebagai argumen.

David Hammen
sumber
7

Penutupan setara dengan objek yang menerapkan metode run (), dan sebaliknya, objek dapat ditiru dengan penutupan.

  • Keuntungan dari penutupan adalah bahwa mereka dapat digunakan dengan mudah di mana saja Anda mengharapkan suatu fungsi: alias fungsi tingkat tinggi, panggilan balik sederhana (atau Pola Strategi). Anda tidak perlu mendefinisikan antarmuka / kelas untuk membangun penutupan ad-hoc.

  • Keuntungan dari objek adalah kemungkinan untuk memiliki interaksi yang lebih kompleks: beberapa metode dan / atau antarmuka yang berbeda.

Jadi, menggunakan penutupan atau benda sebagian besar adalah masalah gaya. Berikut adalah contoh hal-hal yang membuat penutupan mudah tetapi tidak nyaman untuk diterapkan dengan objek:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

Pada dasarnya, Anda merangkum status tersembunyi yang diakses hanya melalui penutupan global: Anda tidak perlu merujuk ke objek apa pun, hanya menggunakan protokol yang ditentukan oleh tiga fungsi.


  • Saya memperluas jawaban untuk menanggapi komentar ini dari supercat *.

Saya percaya komentar pertama supercat pada fakta bahwa dalam beberapa bahasa, adalah mungkin untuk mengontrol umur objek secara tepat, sedangkan hal yang sama tidak berlaku untuk penutupan. Akan tetapi, dalam kasus bahasa yang dikumpulkan sampah, masa pakai objek umumnya tidak terbatas, dan dengan demikian dimungkinkan untuk membangun penutup yang bisa disebut dalam konteks dinamis di mana ia tidak boleh dipanggil (membaca dari penutup setelah aliran ditutup, misalnya).

Namun, cukup sederhana untuk mencegah penyalahgunaan dengan menangkap variabel kontrol yang akan menjaga eksekusi penutupan. Lebih tepatnya, inilah yang ada dalam pikiran saya (dalam Common Lisp):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Di sini, kita mengambil penunjuk fungsi functiondan mengembalikan dua penutupan, keduanya menangkap variabel lokal bernama active:

  • yang pertama didelegasikan function, hanya ketika activeitu benar
  • yang kedua diatur actionke nil, alias false.

Alih-alih (when active ...), tentu saja mungkin untuk memiliki (assert active)ekspresi, yang bisa melempar pengecualian kalau-kalau penutupan dipanggil ketika seharusnya tidak. Juga, perlu diingat bahwa kode yang tidak aman mungkin sudah melemparkan pengecualian dengan sendirinya saat digunakan dengan buruk, jadi Anda jarang membutuhkan pembungkus seperti itu.

Inilah cara Anda menggunakannya:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Perhatikan bahwa penutupan yang tidak aktif juga dapat diberikan ke fungsi lain; di sini, activevariabel lokal tidak dibagi antara fdan g; juga, selain active, fhanya merujuk obj1dan ghanya mengacu obj2.

Hal lain yang disebutkan oleh supercat adalah bahwa penutupan dapat menyebabkan kebocoran memori, tetapi sayangnya, ini adalah kasus untuk hampir semua hal di lingkungan yang dikumpulkan sampah. Jika tersedia, ini dapat diselesaikan dengan pointer lemah (penutupan itu sendiri mungkin disimpan dalam memori, tetapi tidak mencegah pengumpulan sampah dari sumber daya lainnya).

coredump
sumber
1
Kerugian dari penutupan seperti yang biasa diterapkan adalah bahwa begitu penutupan yang menggunakan salah satu variabel lokal metode terbuka ke dunia luar, metode yang mendefinisikan penutupan mungkin kehilangan kendali atas kapan variabel itu dibaca dan ditulis. Tidak ada cara yang mudah, dalam kebanyakan bahasa, untuk menentukan penutupan sementara, meneruskannya ke suatu metode, dan menjamin bahwa itu tidak akan ada lagi setelah metode yang menerimanya kembali, juga tidak ada cara dalam bahasa multi-utas untuk menjamin bahwa penutupan tidak akan berjalan dalam konteks threading yang tidak terduga.
supercat
@supercat: Lihatlah Objective-C dan Swift.
gnasher729
1
@supercat Bukankah kelemahan yang sama ada dengan objek stateful?
Andres F.
@AndresF .: Dimungkinkan untuk merangkum objek stateful dalam pembungkus yang mendukung pembatalan; jika kode merangkum suatu yang dipegang secara pribadi List<T>dalam (kelas hipotetis) TemporaryMutableListWrapper<T>dan memaparkannya ke kode luar, dapat dipastikan bahwa jika membatalkan pembungkus, kode luar tidak lagi memiliki cara memanipulasi kode List<T>. Seseorang dapat merancang penutupan untuk memungkinkan pembatalan begitu mereka telah memenuhi tujuan yang diharapkan, tetapi itu hampir tidak nyaman. Penutupan ada untuk membuat pola tertentu nyaman, dan upaya yang diperlukan untuk menjaganya akan meniadakan itu.
supercat
1
@coredump: Dalam C #, jika dua penutupan memiliki variabel yang sama, objek yang dihasilkan kompiler yang sama akan melayani keduanya, karena perubahan yang dibuat ke variabel bersama dengan satu penutupan harus dilihat oleh yang lain. Menghindari pembagian itu akan mengharuskan setiap penutupan menjadi objeknya sendiri yang menyimpan variabel yang tidak dibagi sendiri serta referensi ke objek bersama yang menyimpan variabel bersama. Bukan tidak mungkin, tetapi itu akan menambahkan level dereferencing ekstra untuk sebagian besar akses variabel, memperlambat semuanya.
supercat
6

Tidak ada yang belum dikatakan, tapi mungkin contoh yang lebih sederhana.

Berikut ini contoh JavaScript menggunakan batas waktu:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

Apa yang terjadi di sini, adalah bahwa ketika delayedLog()dipanggil, ia kembali segera setelah mengatur batas waktu, dan batas waktu terus berdetak di latar belakang.

Tetapi ketika batas waktu habis dan memanggil fire()fungsi, konsol akan menampilkan messageyang semula diteruskan delayedLog(), karena masih tersedia fire()melalui penutupan. Anda dapat menelepon delayedLog()sebanyak yang Anda inginkan, dengan pesan yang berbeda dan menunda setiap waktu, dan itu akan melakukan hal yang benar.

Tapi mari kita bayangkan JavaScript tidak memiliki penutupan.

Salah satu caranya adalah membuat setTimeout()pemblokiran - lebih seperti fungsi "tidur" - jadi delayedLog()ruang lingkup tidak hilang sampai batas waktu habis. Tetapi memblokir semuanya tidak terlalu baik.

Cara lain adalah dengan meletakkan messagevariabel di beberapa ruang lingkup lain yang akan dapat diakses setelah delayedLog()ruang lingkup hilang.

Anda dapat menggunakan variabel global - atau setidaknya "cakupan yang lebih luas" - tetapi Anda harus mencari cara untuk melacak pesan apa yang terjadi dengan batas waktu apa. Tapi itu tidak bisa hanya berurutan, antrian FIFO, karena Anda dapat mengatur penundaan yang Anda inginkan. Jadi mungkin "masuk pertama, keluar ketiga" atau sesuatu. Jadi, Anda memerlukan cara lain untuk mengikat fungsi waktunya dengan variabel yang dibutuhkan.

Anda bisa membuat instance objek batas waktu yang "mengelompokkan" timer dengan pesan. Konteks objek kurang lebih merupakan ruang lingkup yang melekat. Maka Anda akan memiliki timer mengeksekusi dalam konteks objek, jadi itu akan memiliki akses ke pesan yang tepat. Tetapi Anda harus menyimpan objek itu karena tanpa referensi apa pun itu akan mengumpulkan sampah (tanpa penutupan, tidak akan ada referensi tersirat untuk itu juga). Dan Anda harus menghapus objek setelah dipecat, jika tidak, objek itu hanya akan bertahan. Jadi, Anda memerlukan semacam daftar objek batas waktu, dan secara berkala memeriksanya untuk menghapus objek "yang dihabiskan" - atau objek akan menambah dan menghapus diri dari daftar, dan ...

Jadi ... ya, ini semakin membosankan.

Untungnya, Anda tidak harus menggunakan cakupan yang lebih luas, atau bertengkar objek hanya untuk menjaga variabel tertentu tetap ada. Karena JavaScript memiliki penutupan, Anda sudah memiliki ruang lingkup yang Anda butuhkan. Cakupan yang memberi Anda akses ke messagevariabel saat Anda membutuhkannya. Dan karena itu, Anda bisa lolos dengan menulis delayedLog()seperti di atas.

Flambino
sumber
Saya memiliki masalah menyebut penutupan . Mungkin saya akan menambahkan penutupan tidak sengaja . messageterlampir dalam lingkup fungsi firedan karena itu disebut dalam panggilan lebih lanjut; tapi itu tidak sengaja mengatakannya. Secara teknis itu adalah penutupan. +1 anyways;)
Thomas Junk
@ ThomasJunk Saya tidak yakin saya cukup ikuti. Bagaimana tampilan " penutupan non- kebetulan"? Saya melihat makeaddercontoh Anda di atas, yang, bagi saya, tampak sama. Anda mengembalikan fungsi "curried" yang mengambil satu arg bukan dua; menggunakan cara yang sama, saya membuat fungsi yang tidak menggunakan argumen. Saya hanya tidak mengembalikannya tetapi meneruskannya sebagai setTimeoutgantinya.
Flambino
»Saya hanya tidak mengembalikannya« mungkin, itulah intinya, yang membuat perbedaan bagi saya. Saya tidak bisa mengartikulasikan "keprihatinan" saya;) Dalam istilah teknis, Anda 100% benar. Referensi messagedalam firemenghasilkan penutupan. Dan ketika itu disebut di setTimeoutdalamnya memanfaatkan keadaan terpelihara.
Thomas Junk
1
@ ThomasJunk Saya melihat bagaimana baunya mungkin sedikit berbeda. Namun, saya mungkin tidak akan berbagi kekhawatiran Anda :) Atau, bagaimanapun juga, saya tidak akan menyebutnya "kebetulan" - cukup yakin saya sengaja mengkodekannya;)
Flambino
3

PHP dapat digunakan untuk membantu menunjukkan contoh nyata dalam bahasa yang berbeda.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

Jadi pada dasarnya saya mendaftarkan fungsi yang akan dieksekusi untuk / api / pengguna URI . Ini sebenarnya adalah fungsi middleware yang akhirnya disimpan di tumpukan. Fungsi lain akan melilitnya. Cukup banyak seperti Node.js / Express.js .

The ketergantungan injeksi kontainer tersedia (melalui penggunaan klausa) dalam fungsi ketika dipanggil. Dimungkinkan untuk membuat semacam kelas tindakan rute, tetapi kode ini ternyata lebih sederhana, lebih cepat, dan lebih mudah dipelihara.

Cerad
sumber
-1

Sebuah penutupan adalah bagian dari kode sewenang-wenang, termasuk variabel, yang dapat ditangani sebagai data kelas.

Contoh sepele adalah qsort lama yang baik: Ini adalah fungsi untuk mengurutkan data. Anda harus memberikannya pointer ke fungsi yang membandingkan dua objek. Jadi, Anda harus menulis fungsi. Fungsi itu mungkin perlu parameter, yang berarti Anda memberikannya variabel statis. Yang berarti tidak aman untuk thread. Anda berada di DS. Jadi, Anda menulis alternatif yang membutuhkan penutupan alih-alih fungsi pointer. Anda langsung memecahkan masalah parameterisasi karena parameter menjadi bagian dari penutupan. Anda membuat kode Anda lebih mudah dibaca karena Anda menulis bagaimana objek dibandingkan secara langsung dengan kode yang memanggil fungsi pengurutan.

Ada banyak situasi di mana Anda ingin melakukan beberapa tindakan yang memerlukan banyak kode pelat ketel, ditambah satu bagian kecil namun penting dari kode yang perlu disesuaikan. Anda menghindari kode boilerplate dengan menulis fungsi sekali yang mengambil parameter penutupan dan melakukan semua kode boilerplate di sekitarnya, dan kemudian Anda dapat memanggil fungsi ini dan meneruskan kode yang akan disesuaikan sebagai penutup. Cara menulis kode yang sangat ringkas dan mudah dibaca.

Anda memiliki fungsi di mana beberapa kode non-sepele perlu dilakukan dalam berbagai situasi. Ini digunakan untuk menghasilkan duplikasi kode atau kode yang berkerut sehingga kode non-sepele akan hadir hanya sekali. Sepele: Anda menetapkan penutupan ke variabel dan menyebutnya dengan cara yang paling jelas di mana pun itu diperlukan.

Multithreading: iOS / MacOS X memiliki fungsi untuk melakukan hal-hal seperti "melakukan penutupan ini pada utas latar", "... pada utas utama", "... pada utas utama, 10 detik dari sekarang". Itu membuat multithreading sepele .

Panggilan asinkron: Itulah yang dilihat OP. Panggilan apa pun yang mengakses internet, atau apa pun yang membutuhkan waktu (seperti membaca koordinat GPS) adalah sesuatu yang tidak dapat Anda tunggu hasilnya. Jadi Anda memiliki fungsi yang melakukan hal-hal di latar belakang, dan kemudian Anda memberikan penutupan untuk memberi tahu mereka apa yang harus dilakukan ketika sudah selesai.

Itu awal yang kecil. Lima situasi di mana penutupan bersifat revolusioner dalam hal menghasilkan kode yang ringkas, mudah dibaca, bergantung, dan efisien.

gnasher729
sumber
-4

Penutupan adalah cara singkat menulis metode yang akan digunakan. Ini menghemat upaya Anda menyatakan dan menulis metode yang terpisah. Ini berguna ketika metode hanya akan digunakan sekali dan definisi metode pendek. Manfaatnya adalah pengetikan berkurang karena tidak perlu menentukan nama fungsi, jenis pengembaliannya atau pengubah aksesnya. Juga, ketika membaca kode Anda tidak perlu mencari di tempat lain untuk definisi metode.

Di atas adalah ringkasan dari Understand Lambda Expressions oleh Dan Avidar.

Ini mengklarifikasi penggunaan penutupan bagi saya karena itu menjelaskan alternatif (penutupan vs metode) dan manfaat masing-masing.

Kode berikut digunakan sekali, dan hanya sekali selama pengaturan. Menulisnya di tempat di bawah viewDidLoad menghemat kesulitan mencarinya di tempat lain, dan mempersingkat ukuran kode.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

Selain itu, ini memberikan proses asinkron untuk menyelesaikan tanpa memblokir bagian lain dari program, dan, penutupan akan mempertahankan nilai untuk digunakan kembali dalam panggilan fungsi berikutnya.

Penutupan lain; ini menangkap nilai ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)
Bendrix
sumber
4
Sayangnya, ini membingungkan penutupan dan lambdas. Lambdas sering menggunakan penutupan, karena mereka biasanya jauh lebih berguna jika didefinisikan a) sangat singkat dan b) di dalam dan tergantung pada konteks metode yang diberikan, termasuk variabel. Namun, penutupan sebenarnya tidak ada hubungannya dengan gagasan lambda, yang pada dasarnya adalah kemampuan untuk mendefinisikan fungsi kelas satu di tempat dan meneruskannya untuk digunakan nanti.
Nathan Tuggy
Saat Anda memilih jawaban ini, baca ini ... Kapan saya harus memilih? Gunakan downvotes Anda setiap kali Anda menemukan posting yang ceroboh, tidak ada upaya dikeluarkan, atau jawaban yang jelas dan mungkin salah salah. Jawaban saya mungkin tidak memiliki beberapa alasan untuk menggunakan penutupan, tetapi itu tidak ceroboh atau salah. Ada banyak makalah dan diskusi tentang penutupan yang berpusat pada pemrograman fungsional dan notasi lambda. Semua jawaban saya mengatakan bahwa penjelasan tentang pemrograman fungsional ini membantu saya memahami penutupan. Itu dan nilai yang gigih.
Bendrix
1
Jawabannya mungkin tidak berbahaya , tetapi tentu saja "jelas [...] salah". Ini menggunakan istilah yang salah untuk konsep yang sangat saling melengkapi dan dengan demikian sering digunakan bersama - persis di mana kejelasan perbedaan sangat penting. Ini kemungkinan akan menyebabkan masalah desain bagi programmer yang membaca ini jika tidak ditangani. (Dan karena, sejauh yang saya tahu, contoh kode tidak mengandung penutupan, hanya fungsi lambda, tidak membantu menjelaskan mengapa penutupan akan membantu.)
Nathan Tuggy
Nathan, contoh kode itu adalah penutupan di Swift. Anda dapat bertanya kepada orang lain jika Anda meragukan saya. Jadi, jika ini adalah penutupan, apakah Anda akan memilihnya?
Bendrix
1
Apple cukup jelas menggambarkan dengan tepat apa yang mereka maksud dengan penutupan dalam dokumentasi mereka . "Penutupan adalah blok fungsionalitas mandiri yang dapat diedarkan dan digunakan dalam kode Anda. Penutupan di Swift mirip dengan blok di C dan Objective-C dan untuk lambdas dalam bahasa pemrograman lain." Ketika Anda sampai pada implementasi dan terminologi itu bisa membingungkan .