Fungsi Karir JavaScript

Pemrograman fungsional adalah gaya pengembangan program di mana beberapa fitur spesifik untuk bekerja dengan fungsi banyak digunakan. Terutama, tentang mentransfer fungsi ke fungsi lain sebagai argumen dan tentang mengembalikan fungsi dari fungsi lain. Konsep "fungsi murni" juga termasuk dalam gaya fungsional pemrograman. Output dari fungsi murni hanya bergantung pada input, mereka, ketika dieksekusi, tidak mempengaruhi keadaan program.

Prinsip pemrograman fungsional didukung oleh banyak bahasa. Diantaranya adalah JavaScript, Haskell, Clojure, Erlang. Penggunaan mekanisme pemrograman fungsional menyiratkan pengetahuan, antara lain, konsep seperti fungsi murni, fungsi kari, fungsi tingkat tinggi.



Materi yang kami terjemahkan hari ini adalah tentang kari. Kami akan berbicara tentang cara kerja currying dan bagaimana pengetahuan tentang mekanisme ini dapat bermanfaat bagi pengembang JS.

Apa itu kari?


Currying dalam pemrograman fungsional adalah transformasi fungsi dengan banyak argumen menjadi satu set fungsi bersarang dengan satu argumen. Ketika fungsi curried dipanggil dengan satu argumen yang diteruskan ke sana, ia mengembalikan fungsi baru yang mengharapkan argumen berikutnya tiba. Fungsi-fungsi baru yang menunggu argumen berikutnya dikembalikan setiap kali fungsi yang dipanggil dipanggil - sampai fungsi menerima semua argumen yang diperlukan. Argumen yang diperoleh sebelumnya, berkat mekanisme penutupan, sedang menunggu saat ketika fungsi mendapatkan semua yang diperlukan untuk melakukan perhitungan. Setelah menerima argumen terakhir, fungsi melakukan perhitungan dan mengembalikan hasilnya.

Berbicara tentang currying , kita dapat mengatakan bahwa ini adalah proses mengubah fungsi dengan beberapa argumen menjadi fungsi dengan arity yang lebih sedikit.

Arity adalah jumlah argumen ke suatu fungsi. Sebagai contoh, berikut adalah deklarasi sepasang fungsi:

function fn(a, b) {    //... } function _fn(a, b, c) {    //... } 

Fungsi fn mengambil dua argumen (ini adalah fungsi biner atau 2-ary), fungsi _fn mengambil tiga argumen (fungsi ternary, 3-ary).

Mari kita bicara tentang situasi ketika, selama kari, fungsi dengan beberapa argumen dikonversi ke satu set fungsi, yang masing-masing mengambil satu argumen.

Pertimbangkan sebuah contoh. Kami memiliki fungsi berikut:

 function multiply(a, b, c) {   return a * b * c; } 

Dibutuhkan tiga argumen dan mengembalikan produk mereka:

 multiply(1,2,3); // 6 

Sekarang mari kita pikirkan bagaimana cara mengubahnya menjadi seperangkat fungsi, yang masing-masing membutuhkan satu argumen. Mari kita buat versi kari dari fungsi ini dan lihat bagaimana mendapatkan hasil yang sama saat memanggil beberapa fungsi:

 function multiply(a) {   return (b) => {       return (c) => {           return a * b * c       }   } } log(multiply(1)(2)(3)) // 6 

Seperti yang Anda lihat, di sini kami mengonversi panggilan menjadi fungsi tunggal dengan tiga argumen - multiply(1,2,3) menjadi panggilan ke tiga fungsi - multiply(1)(2)(3) .

Ternyata satu fungsi telah berubah menjadi beberapa fungsi. Saat menggunakan konstruksi baru, setiap fungsi, kecuali yang terakhir, mengembalikan hasil perhitungan, mengambil argumen dan mengembalikan fungsi lainnya, juga mampu menerima argumen dan mengembalikan fungsi lainnya. Jika konstruksi dari formulir ini multiply(1)(2)(3) tidak terlihat jelas bagi Anda, mari tulis dalam formulir ini untuk lebih memahami hal ini:

 const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result); // 6 

Sekarang mari kita baris demi baris apa yang terjadi di sini.

Pertama, kita meneruskan argumen 1 ke fungsi multiply :

 const mul1 = multiply(1); 

Ketika fungsi ini bekerja, desain ini berfungsi:

 return (b) => {       return (c) => {           return a * b * c       }   } 

Sekarang mul1 memiliki referensi ke fungsi yang mengambil argumen b . Kami memanggil fungsi mul1 , meneruskannya 2 :

 const mul2 = mul1(2); 

Sebagai hasil dari panggilan ini, kode berikut akan dieksekusi:

 return (c) => {           return a * b * c       } 

mul2 akan berisi referensi ke fungsi yang mungkin ada di dalamnya, misalnya, sebagai hasil dari operasi berikut:

 mul2 = (c) => {           return a * b * c       } 

Jika sekarang kita memanggil fungsi mul2 , meneruskannya 3 , maka fungsi akan melakukan perhitungan yang diperlukan menggunakan argumen a dan b :

 const result = mul2(3); 

Hasil perhitungan ini adalah 6 :

 log(result); // 6 

Fungsi mul2 , yang memiliki tingkat sarang tertinggi, memiliki akses ke ruang lingkup, ke penutupan yang dibentuk oleh fungsi multiply dan mul1 . Itulah sebabnya dalam fungsi mul2 dimungkinkan untuk melakukan perhitungan dengan variabel yang dideklarasikan dalam fungsi yang pelaksanaannya telah selesai, yang telah mengembalikan beberapa nilai dan diproses oleh pengumpul sampah.

Di atas kami memeriksa contoh abstrak, tetapi, pada dasarnya, fungsi yang sama, yang dirancang untuk menghitung volume kotak persegi panjang.

 function volume(l,w,h) {   return l * w * h; } const vol = volume(100,20,90) // 180000 

Begini tampilannya seperti kari:

 function volume(l) {   return (w) => {       return (h) => {           return l * w * h       }   } } const vol = volume(100)(20)(90) // 180000 

Jadi, kari didasarkan pada ide berikut: berdasarkan fungsi tertentu, fungsi lain dibuat yang mengembalikan fungsi khusus.

Currying dan penggunaan sebagian fungsi


Sekarang, mungkin, ada perasaan bahwa jumlah fungsi bersarang, ketika mewakili fungsi sebagai satu set fungsi bersarang, tergantung pada jumlah argumen ke fungsi. Dan jika itu menyangkut kari, maka itu adalah kari.

Versi khusus fungsi untuk menghitung volume, yang telah kita lihat, dapat dibuat sebagai berikut:

 function volume(l) {   return (w, h) => {       return l * w * h   } } 

Di sini ide diterapkan, sangat mirip dengan yang dibahas di atas. Anda dapat menggunakan fungsi ini sebagai berikut:

 const hV = volume(70); hV(203,142); hV(220,122); hV(120,123); 

Dan Anda dapat melakukan ini:

 volume(70)(90,30); volume(70)(390,320); volume(70)(940,340); 

Bahkan, di sini Anda dapat melihat bagaimana kami, dengan perintah volume(70) , menciptakan fungsi khusus untuk menghitung volume benda, salah satu dimensi yang (yaitu, panjang, l ) diperbaiki. Fungsi volume mengharapkan 3 argumen dan berisi 2 fungsi bersarang, tidak seperti versi sebelumnya dari fungsi yang sama, versi kari yang berisi 3 fungsi bersarang.

Fungsi yang diperoleh setelah memanggil volume(70) mengimplementasikan konsep aplikasi fungsi parsial. Aplikasi fungsi kari dan parsial sangat mirip satu sama lain, tetapi konsepnya berbeda.

Dalam aplikasi parsial, fungsi diubah menjadi fungsi lain dengan argumen yang lebih sedikit (less arity). Beberapa argumen dari fungsi semacam itu sudah diperbaiki (nilai default ditetapkan untuk mereka).

Misalnya, ada fungsi seperti itu:

 function acidityRatio(x, y, z) {   return performOp(x,y,z) } 

Itu dapat dikonversi menjadi ini:

 function acidityRatio(x) {   return (y,z) => {       return performOp(x,y,z)   } } 

Implementasi fungsi performOp() tidak diberikan di sini, karena tidak mempengaruhi konsep yang sedang dipertimbangkan.

Fungsi yang dapat diperoleh dengan memanggil fungsi acidityRatio() dengan argumen yang nilainya perlu diperbaiki adalah fungsi asli, salah satu argumen yang diperbaiki, dan fungsi ini sendiri membutuhkan satu argumen lebih sedikit dari aslinya.

Versi fungsi yang kari akan terlihat seperti ini:

 function acidityRatio(x) {   return (y) = > {       return (z) = > {           return performOp(x,y,z)       }   } } 

Seperti yang dapat Anda lihat, saat kari, jumlah fungsi bersarang sama dengan jumlah argumen dari fungsi asli. Masing-masing fungsi mengharapkan argumennya sendiri. Jelas bahwa jika fungsi argumen tidak menerima, atau hanya menerima satu argumen, maka itu tidak dapat digulung.

Dalam situasi di mana fungsi memiliki dua argumen, hasil currying dan aplikasi parsial dapat dikatakan bertepatan. Sebagai contoh, kami memiliki fungsi seperti itu:

 function div(x,y) {   return x/y; } 

Misalkan kita perlu menulis ulang sehingga kita bisa, memperbaiki argumen pertama, mendapatkan fungsi yang melakukan perhitungan ketika hanya meneruskan argumen kedua, yaitu, kita perlu menerapkan fungsi ini secara parsial. Ini akan terlihat seperti ini:

 function div(x) {   return (y) => {       return x/y;   } } 

Hasil curryingnya akan terlihat sama persis.

Pada aplikasi praktis dari konsep currying dan penerapan fungsi parsial


Aplikasi fungsi kari dan parsial dapat berguna dalam berbagai situasi. Misalnya, ketika mengembangkan modul kecil yang cocok untuk digunakan kembali.

Penggunaan sebagian fungsi membuatnya lebih mudah untuk menggunakan modul universal. Misalnya, kami memiliki toko online, dalam kode yang ada fungsi yang digunakan untuk menghitung jumlah yang harus dibayar dengan mempertimbangkan diskon.

 function discount(price, discount) {   return price * discount } 

Ada kategori pelanggan tertentu, sebut saja mereka “pelanggan tercinta”, kepada siapa kami memberikan diskon 10%. Misalnya, jika klien seperti itu membeli sesuatu seharga $ 500, kami memberinya diskon sebesar $ 50:

 const price = discount(500,0.10); // $50 // $500 - $50 = $450 

Sangat mudah untuk memperhatikan bahwa dengan pendekatan ini, kita harus terus memanggil fungsi ini dengan dua argumen:

 const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270 

Fungsi asli dapat dikurangi menjadi bentuk yang memungkinkan Anda menerima fungsi baru dengan tingkat diskon yang telah ditentukan, setelah memanggil mana cukup untuk mentransfer jumlah pembelian. Fungsi discount() dalam contoh kami memiliki dua argumen. Berikut tampilannya di mana kami mengonversinya:

 function discount(discount) {   return (price) => {       return price * discount;   } } const tenPercentDiscount = discount(0.1); 

Fungsi tenPercentDiscount() adalah hasil dari aplikasi parsial dari fungsi discount() . Saat memanggil tenPercentDiscount() fungsi ini, itu sudah cukup untuk melewati harga, dan diskon 10%, yaitu, argumen discount , sudah akan ditetapkan:

 tenPercentDiscount(500); // $50 // $500 - $50 = $450 

Jika ada pembeli di toko kami yang telah memutuskan untuk memberikan diskon 20%, maka Anda bisa mendapatkan fungsi yang sesuai untuk bekerja dengan mereka seperti ini:

 const twentyPercentDiscount = discount(0.2); 

Sekarang fungsi twentyPercentDiscount() dapat dipanggil untuk menghitung harga pokok barang, dengan mempertimbangkan diskon 20%:

 twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000 

Fungsi universal untuk aplikasi parsial fungsi lainnya


Kami akan mengembangkan fungsi yang menerima fungsi apa pun dan mengembalikan variannya, yang merupakan fungsi, beberapa argumen yang telah ditetapkan. Berikut adalah kode yang memungkinkan Anda melakukan ini (Anda, jika Anda ingin mengembangkan fungsi yang sama, sangat mungkin Anda akan mendapatkan sesuatu yang lain sebagai hasilnya):

 function partial(fn, ...args) {   return (..._arg) => {       return fn(...args, ..._arg);   } } 

Fungsi partial() menerima fungsi fn , yang ingin kita konversi ke fungsi yang diterapkan sebagian, dan sejumlah variabel parameter (...args ). Pernyataan rest digunakan untuk meletakkan semua parameter setelah fn ke dalam args .

Fungsi ini mengembalikan fungsi lain yang juga menerima sejumlah variabel parameter ( _arg ). Fungsi ini, pada gilirannya, memanggil fungsi fn asli, memberikan parameter ...args dan ..._arg (menggunakan operator spread ). Fungsi melakukan perhitungan dan mengembalikan hasilnya.

Kami akan menggunakan fungsi ini untuk membuat varian fungsi volume sudah tidak asing lagi bagi Anda, dirancang untuk menghitung volume parallelepipeds persegi panjang, salah satu sisi yang diperbaiki:

 function volume(l,h,w) {   return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000 

Di sini Anda dapat menemukan contoh fungsi universal untuk menjelajah fungsi lain.

Ringkasan


Pada artikel ini, kita berbicara tentang aplikasi fungsi currying dan parsial. Metode-metode untuk mentransformasikan fungsi-fungsi ini diimplementasikan dalam JavaScript karena penutupan dan karena fakta bahwa fungsi-fungsi di JS adalah objek-objek dari kelas pertama (mereka dapat diteruskan sebagai argumen ke fungsi-fungsi lain, dikembalikan dari mereka, ditugaskan ke variabel).

Pembaca yang budiman! Apakah Anda menggunakan teknik currying dan penerapan sebagian fungsi dalam proyek Anda?

Source: https://habr.com/ru/post/id427295/


All Articles