Beberapa tahun yang lalu, saya pernah membuat sebuah post tentang functional programming, "Suka ngoding? Belajarlah Haskell setidaknya sekali seumur hidup".

Saya masih ingat, saat itu saya masih menggunakan ClojureScript untuk day to day pekerjaan saya.

Saya merasa, dengan ClojureScript saya bisa lebih produktif dibandingkan pure JavaScript. Dan kenapa saya mention Haskell? karena Haskell adalah bahasa pure functional yang mengubah paradigma saya dalam programming.

Berbeda dengan ClojureScript yang tidak memaksa menulis pure function. Di Haskell kita dipaksa untuk menulis kode pure function.

Sekarang, day to day pekerjaan saya tidak lagi menggunakan ClojureScript, tetapi JavaScript/TypeScript. Tetapi, ClojureScript dan Haskell sangat berpengaruh dalam style programming saya.

Sebelum mengetahui functional programming, kode JavaSript yang saya tulis sangatlah rumit dan rawan error dan sulit untuk scale.

Setelah beberapa tahun saya dipaksa menulis dengan style functional di Clojure/ClojureScript. Kini saya menulis JavaScript bisa lebih worry-free. Meskipun JavaScript bukanlah bahasa pemrograman functional, tetapi kita tetap bisa melakukan style functional.

Bagai yang belum tahu ClojureScript, ClojureScript adalah bahasa pemrograman fungsional yang di compile ke JavaScript.

Dan Haskell adalah bahasa pemrorgaman fungsional juga, tetapi tidak di compile ke JavaScript, tetapi ke binary langsung sama seperti C/C++.

Kenapa Functional Programming?

Dalam mempelajari sesuatu, selalu saya bertanya "why" nya terlebih dahulu. Dengan tahu apa masalah-nya, apa yang coba di solve, akan relatif lebih mudah mempelajarinya dibandingkan asal telan saja.

Nah, begitupun sekarang, saya mulai dari "why" nya terlebih dahulu.

Kenapa functional programming? Apa sih yang coba di solve functional programming ini?

Untuk memahami functional programming, saya akan kasih contoh problem langsung. Problemnya adalah:

Let A; is arbitrary array/list with string value in it. Convert A to Hashmap with the key of Hashmap is value of array, and the value of Hashmap is index of array.

Intinya, kita akan konversi array ke dalam bentuk map. Contohnya, jika kita punya array seperti ini:

var A = ["a", "b", "c", "d", "e"]

kita akan membuat suatu fungsi yang merubah Array di atas ke dalam map yang bentuknya: {[value]: index}

var newA = {
  "a": 0,
  "b": 1,
  "c": 2,
  "d": 3,
  "e": 4,
}

Nah bagaimana menyelesaikan masalah tersebut?

Untuk menyelesaikan masalah tersebut dalam style imperative (style yang biasanya cara menulis kodenya dari atas ke bawah). Kita akan memerlukan sebuah looping dan variable yang meng-hold index looping tersebut.

function arrStringToMap(elements) {
	var i = 0;
	var results = {};

	while (i < elements.length) {
		var element = results[elements[i - 1]];
		if (element !== undefined) {
			results[elements[i]] = element + 1;
		} else {
			results[elements[i]] = 0;
		}
		i++
	}
	return results;
}

Jika teman-teman coba jalankan kode di atas. Kode di atas akan berjalan sebagaimana yang kita harapkan.

Untuk memahami fungsi arrStringToMap di sini kita harus memahami step by step setiap statemen di dalamnya di jalankan. Kita harus trace bagaimana variable i dengan value-nya dan results sebagai holder.

Kita juga harus mengecek kondisi inisialisasi dan loop selanjutnya.

Sampai di-sini, coba teman-teman pahami dulu kode di atas. Jika sudah paham teman-teman bisa membaca tulisan selanjutnya di bawah.

Less Brain Power vs More Brain Power

Yang menarik dari functional programming adalah abstraksi dalam menyelesaikan suatu masalah.

Jika dalam style imperative yang kita pikirkan adalah line by line setiap statement dari kode (bisanya di baca dari atas ke bawah);

Dalam functional programming yang kita pikirkan adalah ekspresi kode. Kode di baca dari paling luar, dan cukup. Tidak perlu tahu detail bagaimana kode itu bekerja. Karena functinal programming menjamin konsistensi.

Jika teman-teman pernah manggunakan SQL, membaca kode di functional programing mirip dengan SQL. Di SQL, teman-teman tentu tidak tahu bagaiamana detail SQL mengambil data. Yang teman-teman pedulikan bagaimana kita meminta data (Query).

Nah, ada 6 prinsip dalam menulis kode dalam style functional programming:

  1. Immutability
  2. Disciplined state
  3. Pure functions and no side effects/disciplined states
  4. First class functions and high order functions
  5. Type systems
  6. Referential transparency

Saya tidak akan membahas semuanya, karena sebetulnya yang saya peduliin hanya dua prinsip (tidak boleh di langgar), jika ingin kode yang saya tulis konsisten.

  1. Pure function
  2. Avoid mutation in place

Dua prinsip di atas, sesungguhnya adalah tiga prinsip pertama dari 6 prinsip di atas. Sisanya menurut saya turunan dari ke dua prinsip yang saya tulis.

Apa itu pure function?

Bayangkan pure function adalah sebuah mesin yang menerima sembarang masukan. Tetapi mesin tersebut slelalu mengeluarkan benda yang sama jika masukannya sama.

Contoh pure function:

function dobleIt(x) {
  return x + x;
}

Dalam fungsi doubleIt di atas, fungsi itu akan selalu memberikan hasil yang sama, selama input x (number) sama. Contih untuh doubleIt(2) akan selalu memberikan hasil 4.

Nah, contoh yang bukan pure function bagaimana?

function surpriseMeNumber() {
  return Math.random();
}

Fungsi surpriseMeNumber bukan pure function, melanggar prinsip no. 3; Pure functions and no side effects/disciplined state, dan konsekuensinya juga melanggar prinsip no. 6; Referential transparency.

Di sana jelas, setiap kali kita meng-invoke fungsi surpriseMeNumber, dia akan memberikan hasil yang berbeda.

Dan side-effect bukan hanya dihasilkan dari random number saha, tetapi I/O juga termasuk side-effect.


Coba revisit lagi fungsi arrStringToMap, dalam fungsi tersebut, kita melihat mutasi di mana-mana. Atau dalam perinsip saya melanggar prinsip no 2; Avoid mutation in place. Meskipun dengan hasil yang sama menghasilkan output yang sama.

Tetapi dengan pure function, kita bisa menyelesaikan masalah yang sama dengan brain power yang relatif lebih sedikit.

function arrStringToMap2(elements) {
	return elements.reduce(function(results, element, index) {
		return { ...results, [element]: index };
	}, {});
}

Lihat, bagaimana kode arrStringToMap bisa kita tulis lebih compact menggunakan style functional.

Jika teman-teman belum terbiasa dengan functional programming, mungkin teman-teman akan membaca kode arrStringToMap2 di atas dari atas ke bawah. Dan mungkin membuat teman-teman bingung.

Tetapi, bagi yang terbiasa menulis dengan style functional; Mereka akan membacanya per ekpresi dan tidak akan mempedulikan detail.

Bagi orang functional, cara membaca kode arrStringToMap2 di atas adalah cukup membaca elements.reduce dan bagian { ...results, [element]: index }. Dan mereka sudah memahami fungsi ini melakukan apa.

Coba kita bandingkan sekali lagi:

function arrStringToMap(elements) {
	var i = 0;
	var results = {};

	while (i < elements.length) {
		var element = results[elements[i - 1]];
		if (element !== undefined) {
			results[elements[i]] = element + 1;
		} else {
			results[elements[i]] = 0;
		}
		i++
	}
	return results;
}
function arrStringToMap2(elements) {
	return elements.reduce(function(results, element, index) {
		return { ...results, [element]: index };
	}, {});
}

Di atas bisa kita lihat perbandingkan ke-dua kode yang menyelesaikan masalah yang sama. Kode kedua lebih singkat dan tidak memerlukan waktu yang banyak untuk memahaminya (dengan catatan mengerti reduce).

Untuk memahami kode pertama, teman-teman harus membaca kode dari atas ke bawah, mencatat setiap perubahan state; Dan ini sangat rawan error.

Jika saja kedua fungsi tersebut diberi nama sembarang, kita tidak akan tahu fungsi ini melakukan apa. Tetapi di fungsi kedua, kita bisa langsung tahu dengan sekilas melihat saja.

Kode pertama meskipun konsisten menghasilkan keluaran yang sama dengan input yang sama, tetapi tidak pure. Karena ada mutasi di mana-mana. Cara membacanya pun relatif sulit.

Kode kedua, adalah contoh pure function. Tidak ada mutasi, dan selalu menghasilkan keluaran yang sama dengan input yang sama.

Map, Filter, Reduce

Setelah kita tahu prinsip-prinsip yang tidak boleh di langgar dalam menulis style kode functional. Selanjutnya kita bisa mulai menulis kode-kode yang lebih advance, yang bisanya jadi steping stone dalam memahami functional programming.

Jika kita amati dalam style imperative. Masalah yang sering muncul adalah masalah looping.
1. Bagaiamana merubah satu list ke bentuk list yang lain;
2. Bagaimana mengambil beberapa elemen dari sebuah list;
3. Bagaimana men-transform list kedalam bentuk yang lebih compact;

Untuk problem nomor satu, bagaiamana merubah satu list ke bentuk list yang lain. Di imperative style biasanya kita selesaikan dengan looping menggunakan for loop.

Contoh: Kali dua setiap elemen dalam array.

var a = [1, 2, 3, 4];

for (i = 0; i < a.length; i++) {
  a[i] = a[i] * 2;
}

Di functional programming, masalah ini bisa kita selesaikan dengan fungsi .map. Map akan melakukan maping setiap elemen ke dalam suatu fungsi.

var a = [1, 2, 3, 4];

var newA = a.map(functiion(el) {
  return a * 2;
});

Untuk problem no 2, bagaimana mengambil elemen dari sebuah list. Dalam imperative biasanya kita selesaikan dengan adanya kondisional dalam for loop.

Contoh: Cari tahu bus yang siap berangkat.

var buses = [{'name': 'Budiman', 'isReady': false}, {'name': 'Doa Ibu', 'isReady': true}, {'name': 'Prima Jasa', 'isReady': true}];

var activeBuses = [];

for (var i = 0; i < buses.length; i++) {
    var temp = buses[i]; 
    if (temp['isReady']) { 
      activeBuses.push(temp);
  }
}

Di functional programming masalah ini dapat diselesaikan dengan fungsi .filter. Filter akan meng-apply sebuah fungsi yang mengembalikan boolean. Dan untuk setiap elemen yang mengembalikan boolean true akan diambil elemennya.

var buses = [{'name': 'Budiman', 'isReady': false}, {'name': 'Doa Ibu', 'isReady': true}, {'name': 'Prima Jasa', 'isReady': true}];

var activeBuses = buses.filter(function(bus) {
  return bus['isReady'];
});

Terakhir problem ke-3; Bagaimana men-transform list kedalam bentuk yang lebih compact. Di functional programing problem ini disebut reduce atau folding.

Teman-teman bisa membaca dokumentasi reduce di MDN.

Contohnya telah teman-teman baca di fungsi merubah array ke Hashmap di atas. Contoh ini merupakan masalah folding.


Dengan mengetahui tiga fungsi di atas, .map, .filter, dan .reduce kita sudah bisa menyelesaikan setiap masalah apapun seperti pada imperative style.

Dan functional programming adalah sebuah style bagaimana kita menyelesaikan masalah tanpa harus mempedulikan detail bagaimana setiap line dari kode.

Keunggulan dari functional programming adalah energi yang kita habiskan relatif lebih sedikit dibandingkan menggunakan imerative programming.

JavaScript sendiri adalah bahasa yang fleksibel. Dia bisa melakukan imperative style, functional style, atau keduanya.

Jika teman-teman tertarik dengan functional programming, teman-teman bisa mencoba bahasa Haskell. Di Haskell kita dipaksa untuk menulis kode yang benar-benar pure. Dan karena itu akan sedikit banyak memberikan kita cara pandang lain dalam programming.

BTW, ini adalah tulisan yang saya tranlasikan dari persentasi saya tentang functional programming di slide ini.

Have fun and happy coding 😁 !