Pengantar Pemrograman Fungsional: Paradigma JavaScript

Diterbitkan: 2022-03-11

Pemrograman fungsional adalah paradigma membangun program komputer menggunakan ekspresi dan fungsi tanpa mengubah status dan data.

Dengan menghormati batasan ini, pemrograman fungsional bertujuan untuk menulis kode yang lebih jelas untuk dipahami dan lebih tahan bug. Ini dicapai dengan menghindari penggunaan pernyataan kontrol aliran ( for , while , break , continue , goto ) yang membuat kode lebih sulit untuk diikuti. Juga, pemrograman fungsional mengharuskan kita untuk menulis fungsi deterministik murni yang cenderung tidak bermasalah.

Pada artikel ini, kita akan berbicara tentang melakukan pemrograman fungsional menggunakan JavaScript. Kami juga akan mengeksplorasi berbagai metode dan fitur JavaScript yang memungkinkan. Pada akhirnya, kita akan mengeksplorasi berbagai konsep yang terkait dengan pemrograman fungsional dan melihat mengapa mereka begitu kuat.

Sebelum masuk ke pemrograman fungsional, seseorang perlu memahami perbedaan antara fungsi murni dan tidak murni.

Fungsi Murni vs. Tidak Murni

Fungsi murni mengambil beberapa input dan memberikan output tetap. Juga, mereka tidak menyebabkan efek samping di dunia luar.

 const add = (a, b) => a + b;

Di sini, add adalah fungsi murni. Ini karena, untuk nilai a dan b yang tetap, outputnya akan selalu sama.

 const SECRET = 42; const getId = (a) => SECRET * a;

getId bukan fungsi murni. Alasannya karena ia menggunakan variabel global SECRET untuk menghitung output. Jika SECRET diubah, fungsi getId akan mengembalikan nilai yang berbeda untuk input yang sama. Jadi, itu bukan fungsi murni.

 let id_count = 0; const getId = () => ++id_count;

Ini juga merupakan fungsi yang tidak murni, dan itu juga karena beberapa alasan—(1) ia menggunakan variabel non-lokal untuk menghitung outputnya, dan (2) ia menciptakan efek samping di dunia luar dengan memodifikasi variabel di dalamnya. dunia.

getId adalah ilustrasi yang tidak murni

Ini bisa merepotkan jika kita harus men-debug kode ini.

Berapa nilai id_count saat ini? Fungsi lain mana yang memodifikasi id_count ? Apakah ada fungsi lain yang mengandalkan id_count ?

Karena alasan ini, kami hanya menggunakan fungsi murni dalam pemrograman fungsional.

Manfaat lain dari fungsi murni adalah bahwa mereka dapat diparalelkan dan di memo. Lihat dua fungsi sebelumnya. Tidak mungkin untuk memparalelkan atau memoize mereka. Ini membantu dalam membuat kode berkinerja.

Prinsip Pemrograman Fungsional

Sejauh ini, kita telah mempelajari bahwa pemrograman fungsional bergantung pada beberapa aturan. Mereka adalah sebagai berikut.

  1. Jangan mutasi data
  2. Gunakan fungsi murni: output tetap untuk input tetap, dan tanpa efek samping
  3. Gunakan ekspresi dan deklarasi

Ketika kami memenuhi kondisi ini, kami dapat mengatakan bahwa kode kami berfungsi.

Pemrograman Fungsional dalam JavaScript

JavaScript sudah memiliki beberapa fungsi yang memungkinkan pemrograman fungsional. Contoh: String.prototype.slice, Array.protoype.filter, Array.prototype.join.

Di sisi lain, Array.prototype.forEach, Array.prototype.push adalah fungsi yang tidak murni.

Orang dapat berargumen bahwa Array.prototype.forEach bukanlah fungsi yang tidak murni berdasarkan desain, tetapi pikirkanlah—tidak mungkin melakukan apa pun dengannya kecuali mengubah data non-lokal atau melakukan efek samping. Jadi, tidak apa-apa untuk memasukkannya ke dalam kategori fungsi tidak murni.

Selain itu, JavaScript memiliki deklarasi const, yang sempurna untuk pemrograman fungsional karena kami tidak akan mengubah data apa pun.

Fungsi Murni dalam JavaScript

Mari kita lihat beberapa fungsi murni (metode) yang diberikan oleh JavaScript.

Saring

Seperti namanya, ini memfilter array.

 array.filter(condition);

Kondisi di sini adalah fungsi yang mendapatkan setiap item dari array, dan harus memutuskan apakah akan menyimpan item atau tidak dan mengembalikan nilai boolean yang sebenarnya untuk itu.

 const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]

Perhatikan bahwa filterEven adalah fungsi murni. Jika itu tidak murni, maka itu akan membuat seluruh panggilan filter tidak murni.

Peta

map memetakan setiap item larik ke suatu fungsi dan membuat larik baru berdasarkan nilai kembalian dari pemanggilan fungsi.

 array.map(mapper)

mapper adalah fungsi yang mengambil item array sebagai input dan mengembalikan output.

 const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]

Mengurangi

reduce mengurangi array ke nilai tunggal.

 array.reduce(reducer);

reducer adalah fungsi yang mengambil nilai akumulasi dan item berikutnya dalam array dan mengembalikan nilai baru. Disebut seperti ini untuk semua nilai dalam array, satu demi satu.

 const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6 

mengurangi ilustrasi panggilan

concat

concat menambahkan item baru ke array yang ada untuk membuat array baru. Ini berbeda dari push() dalam arti bahwa push() mengubah data, yang membuatnya tidak murni.

 [1, 2].concat([3, 4]) // [1, 2, 3, 4]

Anda juga dapat melakukan hal yang sama menggunakan operator spread.

 [1, 2, ...[3, 4]]

Object.assign

Object.assign menyalin nilai dari objek yang disediakan ke objek baru. Karena pemrograman fungsional didasarkan pada data yang tidak dapat diubah, kami menggunakannya untuk membuat objek baru berdasarkan objek yang ada.

 const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2

Dengan munculnya ES6, ini juga dapat dilakukan dengan menggunakan operator spread.

 const newObj = {...obj};

Membuat Fungsi Murni Anda Sendiri

Kami juga dapat membuat fungsi murni kami. Mari kita lakukan satu untuk menduplikasi string n kali.

 const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);

Fungsi ini menduplikasi string n kali dan mengembalikan string baru.

 duplicate('hooray!', 3) // hooray!hooray!hooray!

Fungsi tingkat tinggi

Fungsi tingkat tinggi adalah fungsi yang menerima fungsi sebagai argumen dan mengembalikan fungsi. Seringkali, mereka digunakan untuk menambah fungsionalitas suatu fungsi.

 const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };

Dalam contoh di atas, kami membuat fungsi tingkat tinggi withLog yang mengambil fungsi dan mengembalikan fungsi yang mencatat pesan sebelum fungsi yang dibungkus berjalan.

 const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7

withLog HOF dapat digunakan dengan fungsi lain juga dan berfungsi tanpa konflik atau menulis kode tambahan. Inilah keindahan HOF.

 const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!

Seseorang juga dapat memanggilnya tanpa mendefinisikan fungsi penggabungan.

 withLog(hype)('Sale'); // calling hype // Sale!!!

kari

Currying berarti memecah fungsi yang mengambil beberapa argumen menjadi satu atau beberapa tingkat fungsi tingkat tinggi.

Mari kita ambil fungsi add .

 const add = (a, b) => a + b;

Saat kita akan mengaduknya, kita menulis ulang dengan mendistribusikan argumen ke dalam beberapa level sebagai berikut.

 const add = a => { return b => { return a + b; }; }; add(3)(4); // 7

Manfaat dari kari adalah memoisasi. Kami sekarang dapat memoize argumen tertentu dalam panggilan fungsi sehingga mereka dapat digunakan kembali nanti tanpa duplikasi dan perhitungan ulang.

 // assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);

Ini tentu lebih baik daripada menggunakan kedua argumen di mana-mana.

 // (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());

Kami juga dapat memformat ulang fungsi kari agar terlihat ringkas. Ini karena setiap level panggilan fungsi currying adalah pernyataan pengembalian satu baris. Oleh karena itu, kita dapat menggunakan fungsi panah di ES6 untuk melakukan refactor sebagai berikut.

 const add = a => b => a + b;

Komposisi

Dalam matematika, komposisi didefinisikan sebagai melewatkan output dari satu fungsi ke input yang lain sehingga menciptakan output gabungan. Hal yang sama dimungkinkan dalam pemrograman fungsional karena kita menggunakan fungsi murni.

Untuk menunjukkan contoh, mari kita buat beberapa fungsi.

Fungsi pertama adalah range, yang mengambil angka awal a dan angka akhir b dan membuat larik yang terdiri dari angka dari a hingga b .

 const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];

Kemudian kita memiliki fungsi multiply yang mengambil array dan mengalikan semua angka di dalamnya.

 const multiply = arr => arr.reduce((p, a) => p * a);

Kami akan menggunakan fungsi-fungsi ini bersama-sama untuk menghitung faktorial.

 const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720

Fungsi di atas untuk menghitung faktorial mirip dengan f(x) = g(h(x)) , sehingga menunjukkan sifat komposisi.

Kata Penutup

Kami membahas fungsi murni dan tidak murni, pemrograman fungsional, fitur JavaScript baru yang membantunya, dan beberapa konsep kunci dalam pemrograman fungsional.

Kami berharap bagian ini menarik minat Anda dalam pemrograman fungsional dan mungkin memotivasi Anda untuk mencobanya dalam kode Anda. Kami yakin ini akan menjadi pengalaman belajar dan tonggak sejarah dalam perjalanan pengembangan perangkat lunak Anda.

Pemrograman fungsional adalah paradigma yang diteliti dengan baik dan kuat untuk menulis program komputer. Dengan diperkenalkannya ES6, JavaScript memungkinkan pengalaman pemrograman fungsional yang jauh lebih baik daripada sebelumnya.