Ingat: pemrograman fungsional bukanlah pemrograman dengan fungsi! ! !
23.4 Pemrograman Fungsional
23.4.1 Apa
yang dimaksud dengan pemrograman fungsional? Jika Anda bertanya terus terang, Anda akan menemukan bahwa itu adalah konsep yang tidak mudah untuk dijelaskan. Banyak veteran dengan pengalaman bertahun-tahun di bidang pemrograman tidak dapat menjelaskan dengan jelas apa yang dipelajari dalam pemrograman fungsional. Pemrograman fungsional memang merupakan bidang yang asing bagi programmer yang akrab dengan pemrograman prosedural. Konsep penutupan, kelanjutan, dan kari sepertinya masih asing bagi kita. Konsep if, else, dan while tidak memiliki kesamaan. Meskipun pemrograman fungsional memiliki prototipe matematika yang indah yang tidak dapat ditandingi oleh pemrograman prosedural, pemrograman ini sangat misterius sehingga hanya mereka yang memiliki gelar PhD yang dapat menguasainya.
Tip: Bagian ini agak sulit, tetapi ini bukan keterampilan yang diperlukan untuk menguasai JavaScript jika Anda tidak ingin menggunakan JavaScript untuk menyelesaikan tugas-tugas yang dilakukan di Lisp, atau tidak ingin mempelajari keterampilan esoteris. pemrograman fungsional, Anda dapat melewatinya dan memasuki bab berikutnya dari perjalanan Anda.
Jadi kembali ke pertanyaan, apa itu pemrograman fungsional? Jawabannya panjang…
Hukum pertama pemrograman fungsional: Fungsi adalah tipe pertama.
Bagaimana seharusnya kalimat ini dipahami? Apa sebenarnya Tipe Satu itu? Mari kita lihat konsep matematika berikut:
persamaan biner F(x, y) = 0, x, y adalah variabel, tuliskan sebagai y = f(x), x adalah parameter, y adalah nilai kembalian, f dari x ke y Hubungan pemetaan disebut fungsi. Jika ada, G(x, y, z) = 0, atau z = g(x, y), g adalah hubungan pemetaan dari x, y ke z, dan juga merupakan fungsi. Jika parameter x dan y dari g memenuhi hubungan sebelumnya y = f(x), maka kita mendapatkan z = g(x, y) = g(x, f(x)). x) adalah fungsi pada x dan parameter fungsi g. Kedua, g adalah fungsi tingkat tinggi dari f.
Dengan cara ini, kita menggunakan z = g(x, f(x)) untuk mewakili solusi terkait persamaan F(x, y) = 0 dan G(x, y, z) = 0, yang merupakan fungsi iteratif . Kita juga bisa menyatakan g dalam bentuk lain, ingat z = g(x, y, f), sehingga kita menggeneralisasikan fungsi g ke dalam fungsi orde tinggi. Dibandingkan dengan yang sebelumnya, keuntungan dari representasi yang terakhir adalah modelnya lebih umum, seperti solusi terkait T(x,y) = 0 dan G(x,y,z) = 0. Kami juga Ini dapat dinyatakan dalam bentuk yang sama (misalkan f=t). Dalam sistem bahasa yang mendukung iterasi untuk mengubah solusi suatu masalah menjadi fungsi tingkat tinggi, fungsi tersebut disebut "tipe pertama".
Fungsi dalam JavaScript jelas merupakan "tipe pertama". Berikut adalah contoh tipikal:
Array.prototype.each = fungsi(penutupan)
{
kembalikan ini.panjang ? [penutupan(ini[0])].concat(ini.slice(1).each(penutupan)) : [];
}
Ini benar-benar kode ajaib ajaib, yang memberikan pesona gaya fungsional sepenuhnya. Hanya ada fungsi dan simbol di seluruh kode. Bentuknya sederhana dan sangat kuat.
[1,2,3,4].each(function(x){return x * 2}) mendapat [2,4,6,8], sedangkan [1,2,3,4].each(function(x ){return x-1}) mendapat [0,1,2,3].
Inti dari fungsional dan berorientasi objek adalah "Tao mengikuti alam". Jika berorientasi objek adalah simulasi dunia nyata, maka ekspresi fungsional adalah simulasi dunia matematika. Dalam arti tertentu, tingkat abstraksinya lebih tinggi daripada berorientasi objek, karena sistem matematika secara inheren memiliki ciri-ciri yang sifatnya tidak ada bandingannya. abstraksi.
Hukum kedua pemrograman fungsional: Penutupan adalah sahabat pemrograman fungsional.
Penutupan, seperti yang telah kami jelaskan di bab sebelumnya, sangat penting untuk pemrograman fungsional. Fitur terbesarnya adalah Anda dapat langsung mengakses lingkungan luar dari lapisan dalam tanpa meneruskan variabel (simbol). Hal ini memberikan kemudahan yang luar biasa bagi program fungsional di bawah banyak sarang
.
{
mengembalikan fungsi innerFun(y)
{
kembalikan x * y;
}
})(2)(3);
Hukum ketiga pemrograman fungsional: fungsi dapat berupa Currying.
Apa itu Currying? Itu konsep yang menarik. Mari kita mulai dengan matematika: katakanlah, perhatikan persamaan ruang tiga dimensi F(x, y, z) = 0, jika kita membatasi z = 0, maka kita mendapatkan F(x, y, 0) = 0, dilambangkan dengan F '(x, kamu). Di sini F' jelas merupakan persamaan baru, yang mewakili proyeksi dua dimensi dari kurva ruang tiga dimensi F(x, y, z) pada bidang z = 0. Dinotasikan y = f(x, z), misalkan z = 0, kita peroleh y = f(x, 0), nyatakan sebagai y = f'(x), kita katakan bahwa fungsi f' adalah solusi Currying dari f .
Contoh Currying JavaScript diberikan di bawah ini:
fungsi tambah(x, y)
{
if(x!=null && y!=null) kembalikan x + y;
else if(x!=null && y==null) mengembalikan fungsi(y)
{
kembalikan x + y;
}
else if(x==null && y!=null) mengembalikan fungsi(x)
{
kembalikan x + y;
}
}
var a = tambah(3, 4);
var b = tambahkan(2);
var c = b(10);
Dalam contoh di atas, b=add(2) menghasilkan fungsi Currying dari add(), yang merupakan fungsi dari parameter y ketika x = 2. Perhatikan bahwa ini juga digunakan di atas Properti penutupan.
Menariknya, kita dapat menggeneralisasi Currying untuk fungsi apa pun, misalnya:
fungsi Foo(x, y, z, w)
{
var args = argumen;
if(Foo.panjang < args.panjang)
fungsi pengembalian()
{
kembali
args.callee.apply(Array.apply([], args).concat(Array.apply([], argumen)));
}
kalau tidak
kembalikan x + y – z * w;
}
Hukum keempat pemrograman fungsional: evaluasi tertunda dan kelanjutan.
//TODO: Pikirkan lagi di sini
23.4.2 Keuntungan
Pengujian Unit
Pemrograman FungsionalSetiap simbol pemrograman fungsional yang ketat mengacu pada hasil kuantitas atau ekspresi langsung, dan tidak ada fungsi yang memiliki efek samping. Karena nilainya tidak pernah diubah di suatu tempat, dan tidak ada fungsi yang mengubah kuantitas di luar cakupannya yang digunakan oleh fungsi lain (seperti anggota kelas atau variabel global). Artinya, hasil evaluasi suatu fungsi hanyalah nilai kembaliannya saja, dan yang mempengaruhi nilai kembaliannya hanyalah parameter fungsi tersebut.
Ini adalah mimpi basah seorang penguji unit. Untuk setiap fungsi dalam program yang diuji, Anda hanya perlu memperhatikan parameternya, tanpa harus mempertimbangkan urutan pemanggilan fungsi, atau mengatur keadaan eksternal dengan hati-hati. Yang harus Anda lakukan adalah meneruskan parameter yang mewakili kasus tepi. Jika setiap fungsi dalam program lolos pengujian unit, Anda memiliki keyakinan yang besar terhadap kualitas perangkat lunak. Namun pemrograman imperatif tidak bisa begitu optimis. Di Java atau C++, tidak cukup hanya memeriksa nilai kembalian suatu fungsi - kita juga harus memverifikasi keadaan eksternal yang mungkin telah diubah oleh fungsi tersebut.
Debugging
Jika program fungsional tidak berperilaku seperti yang Anda harapkan, debugging adalah hal yang mudah. Karena bug dalam program fungsional tidak bergantung pada jalur kode yang tidak terkait dengannya sebelum dieksekusi, masalah yang Anda temui selalu dapat muncul kembali. Dalam program penting, bug muncul dan hilang, karena fungsi dari fungsi tersebut bergantung pada efek samping dari fungsi lainnya, dan Anda mungkin mencari dalam waktu lama ke arah yang tidak terkait dengan terjadinya bug, tetapi tanpa hasil apa pun. Hal ini tidak terjadi pada program fungsional - jika hasil suatu fungsi salah, apa pun yang Anda jalankan sebelumnya, fungsi tersebut akan selalu mengembalikan hasil salah yang sama.
Setelah Anda menciptakan kembali masalahnya, menemukan akar penyebabnya tidak akan sulit dan bahkan mungkin membuat Anda bahagia. Interupsi eksekusi program itu dan periksa tumpukannya. Seperti halnya pemrograman imperatif, parameter setiap pemanggilan fungsi di tumpukan disajikan kepada Anda. Namun dalam program imperatif, parameter ini tidak cukup. Fungsi juga bergantung pada variabel anggota, variabel global, dan status kelas (yang pada gilirannya bergantung pada banyak variabel tersebut). Dalam pemrograman fungsional, suatu fungsi hanya bergantung pada parameternya, dan informasi tersebut ada tepat di depan mata Anda! Selain itu, dalam program imperatif, hanya dengan memeriksa nilai kembalian suatu fungsi tidak dapat memastikan bahwa fungsi tersebut berfungsi dengan benar. Anda harus memeriksa status lusinan objek di luar cakupan fungsi tersebut untuk mengonfirmasi. Dengan program yang fungsional, yang harus Anda lakukan hanyalah melihat nilai kembaliannya!
Periksa parameter dan kembalikan nilai fungsi di sepanjang tumpukan. Segera setelah Anda menemukan hasil yang tidak masuk akal, masukkan fungsi tersebut dan ikuti langkah demi langkah. Ulangi proses ini hingga Anda menemukan titik di mana bug dihasilkan.
Program fungsional paralel dapat dijalankan secara paralel tanpa modifikasi apa pun. Jangan khawatir tentang kebuntuan dan bagian kritis karena Anda tidak pernah menggunakan kunci! Tidak ada data dalam program fungsional yang diubah dua kali oleh thread yang sama, apalagi dua thread berbeda. Ini berarti bahwa thread dapat dengan mudah ditambahkan tanpa berpikir dua kali tanpa menimbulkan masalah tradisional yang mengganggu aplikasi paralel.
Jika demikian, mengapa tidak semua orang menggunakan pemrograman fungsional dalam aplikasi yang memerlukan operasi paralel tinggi? Ya, mereka melakukan itu. Ericsson merancang bahasa fungsional yang disebut Erlang dan menggunakannya dalam saklar telekomunikasi yang memerlukan toleransi kesalahan dan skalabilitas yang sangat tinggi. Banyak orang juga telah mengetahui kelebihan Erlang dan mulai menggunakannya. Kita berbicara tentang sistem kendali telekomunikasi, yang memerlukan keandalan dan skalabilitas yang jauh lebih tinggi dibandingkan sistem biasa yang dirancang untuk Wall Street. Faktanya, sistem Erlang tidak dapat diandalkan dan dapat dikembangkan, sedangkan JavaScriptlah yang dapat diandalkan. Sistem Erlang sangat kokoh.
Kisah tentang paralelisme tidak berhenti di situ. Bahkan jika program Anda adalah single-threaded, kompiler program yang fungsional masih dapat mengoptimalkannya untuk berjalan pada banyak CPU. Silakan lihat kode berikut:
String s1 = agakLongOperation1();
String s2 = agakLongOperation2();
String s3 = concatenate(s1, s2);
Dalam bahasa pemrograman fungsional, kompiler menganalisis kode untuk mengidentifikasi fungsi yang berpotensi memakan waktu yang membuat string s1 dan s2, dan kemudian menjalankannya secara paralel. Hal ini tidak mungkin dilakukan dalam bahasa imperatif, di mana setiap fungsi dapat mengubah keadaan di luar cakupan fungsi dan fungsi selanjutnya mungkin bergantung pada modifikasi ini. Dalam bahasa fungsional, menganalisis fungsi secara otomatis dan mengidentifikasi kandidat yang cocok untuk eksekusi paralel semudah memasukkan fungsi otomatis! Dalam hal ini, pemrograman gaya fungsional adalah "bukti masa depan" (walaupun saya tidak suka menggunakan istilah industri, kali ini saya akan membuat pengecualian). Produsen perangkat keras tidak dapat lagi membuat CPU berjalan lebih cepat, sehingga mereka meningkatkan kecepatan inti prosesor dan mencapai peningkatan kecepatan empat kali lipat karena paralelisme. Tentu saja mereka juga lupa menyebutkan bahwa uang tambahan yang kami keluarkan hanya digunakan untuk perangkat lunak untuk menyelesaikan masalah paralel. Sebagian kecil perangkat lunak penting dan 100% perangkat lunak fungsional dapat dijalankan secara paralel pada mesin ini.
Penerapan kode yang panas
biasanya memerlukan penginstalan pembaruan pada Windows, dan memulai ulang komputer tidak dapat dihindari, dan lebih dari sekali, bahkan jika pemutar media versi baru telah diinstal. Windows XP sangat memperbaiki situasi ini, namun masih belum ideal (saya menjalankan Pembaruan Windows di tempat kerja hari ini, dan sekarang ikon yang mengganggu selalu muncul di baki kecuali saya me-reboot mesin). Sistem Unix selalu berjalan dalam mode yang lebih baik. Saat menginstal pembaruan, hanya komponen terkait sistem yang perlu dihentikan, bukan seluruh sistem operasi. Meski begitu, hal ini masih kurang memuaskan untuk aplikasi server berskala besar. Sistem telekomunikasi harus beroperasi 100% setiap saat karena jika panggilan darurat gagal saat sistem sedang diperbarui, dapat mengakibatkan korban jiwa. Tidak ada alasan perusahaan-perusahaan Wall Street harus menutup layanan selama akhir pekan untuk menginstal pembaruan.
Situasi yang ideal adalah memperbarui kode yang relevan tanpa menghentikan komponen sistem apa pun. Hal ini tidak mungkin terjadi di dunia yang serba imperatif. Pertimbangkan bahwa ketika runtime mengunggah kelas Java dan mengganti definisi baru, semua instance kelas ini tidak akan tersedia karena status simpanannya hilang. Kita dapat mulai menulis beberapa kode kontrol versi yang membosankan untuk memecahkan masalah ini, kemudian membuat serial semua instance dari kelas ini, menghancurkan instance ini, kemudian membuat ulang instance ini dengan definisi baru dari kelas ini, dan kemudian memuat data serial yang sebelumnya dan berharap bahwa pemuatan kode akan mem-porting data tersebut dengan benar ke instance baru. Selain itu, kode porting harus ditulis ulang secara manual untuk setiap pembaruan, dan kehati-hatian harus diberikan untuk mencegah putusnya hubungan antar objek. Teorinya sederhana, namun praktiknya tidak mudah.
Untuk program fungsional, semua status, yaitu parameter yang diteruskan ke fungsi, disimpan di tumpukan, sehingga penerapan panas menjadi mudah! Faktanya, yang perlu kita lakukan hanyalah melakukan perbedaan antara kode yang berfungsi dan versi baru, lalu menerapkan kode baru. Selebihnya akan dilakukan secara otomatis oleh alat bahasa! Jika menurut Anda ini adalah cerita fiksi ilmiah, pikirkan lagi. Selama bertahun-tahun para insinyur Erlang telah memperbarui sistem mereka yang sedang berjalan tanpa mengganggunya.
Penalaran dan Optimasi Berbantuan Mesin
Properti yang menarik dari bahasa fungsional adalah bahwa bahasa tersebut dapat dipikirkan secara matematis. Karena bahasa fungsional hanyalah implementasi dari sistem formal, semua operasi yang dilakukan di atas kertas dapat diterapkan pada program yang ditulis dalam bahasa tersebut. Kompiler dapat menggunakan teori matematika untuk mengubah sepotong kode menjadi kode yang setara namun lebih efisien [7]. Basis data relasional telah menjalani optimasi jenis ini selama bertahun-tahun. Tidak ada alasan mengapa teknik ini tidak dapat diterapkan pada perangkat lunak biasa.
Selain itu, Anda dapat menggunakan teknik ini untuk membuktikan bahwa bagian dari program Anda benar, dan bahkan mungkin membuat alat untuk menganalisis kode Anda dan secara otomatis menghasilkan kasus tepi untuk pengujian unit! Fungsionalitas ini tidak ada gunanya bagi sistem yang tangguh, namun jika Anda merancang alat pacu jantung atau sistem kendali lalu lintas udara, alat ini sangat diperlukan. Jika lamaran yang Anda tulis bukan merupakan tugas inti dalam industri, alat jenis ini juga dapat memberi Anda keunggulan dibandingkan pesaing Anda.
23.4.3 Kerugian dari pemrograman fungsional
Efek samping dari penutupan
Dalam pemrograman fungsional yang tidak ketat, penutupan dapat mengesampingkan lingkungan eksternal (kita telah melihatnya di bab sebelumnya), yang membawa efek samping, dan ketika efek samping tersebut sering terjadi Dan ketika lingkungan di mana program dijalankan sering berubah, kesalahan menjadi sulit dilacak.
//TODO:
bentuk rekursif
Meskipun rekursi sering kali merupakan bentuk ekspresi yang paling ringkas, rekursi tidak seintuitif perulangan non-rekursif.
//TODO:
Kelemahan nilai tertunda
//TODO: