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.
Jawaban:
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 :
Di sini,
threshold
sangat singkat dan alami dikomunikasikan dari mana ia didefinisikan ke tempat itu digunakan. Lingkupnya dibatasi sekecil mungkin.filter
tidak 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:
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.
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.
sumber
bestSellingBooks
kode danfilter
kode, seperti antarmuka spesifik atau argumen data pengguna, agar dapat mengkomunikasikanthreshold
data. Itu mengikat kedua fungsi bersama-sama dengan cara yang tidak dapat digunakan kembali.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:
Tetapi Anda berada di bawah kekuasaan JavaScript tentang bagaimana setiap elemen dalam array akan ditampilkan. Jika Anda ingin mengontrol rendering, Anda bisa melakukan ini:
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:
Keajaiban yang terjadi di sini adalah bahwa
renderInt
mempertahankan akses ketotal
variabel, meskipunrenderInt
berulang 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
sumber
Tujuannya
closures
hanyalah untuk mempertahankan negara; maka namaclosure
- itu menutup negara. Untuk memudahkan penjelasan lebih lanjut, saya akan menggunakan Javascript.Biasanya Anda memiliki fungsi
di mana ruang lingkup variabel terikat pada fungsi ini. Jadi setelah eksekusi, variabel
txt
keluar 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 .
Contoh sederhana, tapi yang tidak terlalu berguna adalah:
Anda mendefinisikan suatu fungsi
makedadder
, yang mengambil satu parameter sebagai input dan mengembalikan fungsi . Ada fungsi luarfunction(a){}
dan batinfunction(b){}{}
. Selanjutnya Anda mendefinisikan (secara implisit) fungsi lainadd5
sebagai hasil dari memanggil funtion orde tinggimakeadder
.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 parameternyaa
adalah5
.Atau untuk menunjukkan setidaknya contoh yang berguna:
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 adalahfunction(){}()
. 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:
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.
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.
sumber
Ada dua kasus penggunaan utama untuk penutupan:
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.
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.
sumber
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 mengerikanrand()
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 sederhanarand()
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.
sumber
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:
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 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):
Di sini, kita mengambil penunjuk fungsi
function
dan mengembalikan dua penutupan, keduanya menangkap variabel lokal bernamaactive
:function
, hanya ketikaactive
itu benaraction
kenil
, aliasfalse
.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:
Perhatikan bahwa penutupan yang tidak aktif juga dapat diberikan ke fungsi lain; di sini,
active
variabel lokal tidak dibagi antaraf
dang
; juga, selainactive
,f
hanya merujukobj1
dang
hanya mengacuobj2
.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).
sumber
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 kodeList<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.Tidak ada yang belum dikatakan, tapi mungkin contoh yang lebih sederhana.
Berikut ini contoh JavaScript menggunakan batas waktu:
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 menampilkanmessage
yang semula diteruskandelayedLog()
, karena masih tersediafire()
melalui penutupan. Anda dapat menelepondelayedLog()
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" - jadidelayedLog()
ruang lingkup tidak hilang sampai batas waktu habis. Tetapi memblokir semuanya tidak terlalu baik.Cara lain adalah dengan meletakkan
message
variabel di beberapa ruang lingkup lain yang akan dapat diakses setelahdelayedLog()
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
message
variabel saat Anda membutuhkannya. Dan karena itu, Anda bisa lolos dengan menulisdelayedLog()
seperti di atas.sumber
message
terlampir dalam lingkup fungsifire
dan karena itu disebut dalam panggilan lebih lanjut; tapi itu tidak sengaja mengatakannya. Secara teknis itu adalah penutupan. +1 anyways;)makeadder
contoh 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 sebagaisetTimeout
gantinya.message
dalamfire
menghasilkan penutupan. Dan ketika itu disebut disetTimeout
dalamnya memanfaatkan keadaan terpelihara.PHP dapat digunakan untuk membantu menunjukkan contoh nyata dalam bahasa yang berbeda.
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.
sumber
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.
sumber
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.
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 ...
sumber