Sebagai seorang pemrogram, kita selalu berurusan dengan data. Karena pemrograman adalah tentang masukan, proses dan keluaran.

Seperti, ketika kita membuat program sederhana untuk menampilkan Hello World, masukan tersebut adalah sebuah variabel yang beripe data string, prosesnya adalah proses penggambaran ke layar (misal via print()), dan keluarannya adalah sebuah tulisan yang muncul di layar baik itu di stdout ataupun di peramban.

Dalam bahasa pemrograman, ada banyak tipe data yang bisa digunakan, dan ketika kita berurusan dengan banyak data, tipe data yang biasa digunakan adalah larik atau Array.

Yang mana data disimpan sebagai/kedalam tumpukan yang bisa diakses berdasarkan index nya.

Sebagai seorang pengembang Frontend, tipe data ini sangat sering digunakan karena Frontend adalah tentang membuat tampilan ke layar yang datanya di bungkus dan bersumber dari basis data yang dikirim oleh Backend.

Sebelum melangkah lebih jauh, anggap kita memiliki data daftar karyawan di PT. Mencari Cinta Sejati:

Nama Jabatan Divisi Tahun Masuk Status
Adam Software Engineer Engineering 2019 Karyawan Tetap
Idris Product Manager Product 2020 Karyawan Tetap
David HR Generalist People 2019 Karyawan Tetap
Dewi UX Engineer Product 2019 Karyawan Tetap
Adit Content Writer Marketing 2020 Probation
Deya Engineering Manager Engineering 2019 Karyawan Tetap

Untuk menampilkan data-data tersebut, yang harus kita lakukan adalah:Ka

  1. Mengetahui jumlah data yang ada
  2. Membuat perulangan sebanyak jumlah data yang ada
  3. Tampilkan bagian data yang diinginkan berdasarkan index dalam proses perulangan tersebut

Sebagai contoh sederhana, kurang-lebih begini kodenya:

// 1. Mengetahui jumlah data yang ada
const totalDataKaryawan = dataKaryawan.length;

// 2. Membuat perulangan sebanyak jumlah data yang ada
// larik umumnya dimulai dari index 0
for (let idx = 0; idx < totalDataKaryawan; idx++) {

  // 3. Tampilkan bagian data yang diinginkan (Nama) berdasarkan
  // index dalam proses perulangan tersebut
  console.log(dataKaryawan[idx]['Nama']) // Adam, Idris, David, ...
}

Ilustrasi diatas hanyalah gambaran tentang bagaimana kita berurusan dengan larik.

Lihat langkah-langkah tersebut, ada yang harus kita lakukan (dari nomor 1 sampai 3) untuk menampilkan sebuah data yang bertipe larik.

Proses tersebut bersifat imperatif, yang singkatnya, kita membuat instruksi tentang "bagaimana cara memprosesnya".

Sebelumnya pernah membahas sedikit tentang Imperatif vs Deklaratif di brief by evilfactorylabs yang sedang hiatus disini, jika tertarik mengetahui sedikit tentang itu, bisa baca dulu tulisan tersebut.

Ada banyak hal (baca: jargon) terkait paradigma fungsional khususnya di JavaScript ini seperti HoF (Higher-order Function), Function composition, dan sebagainya.

Berbicara secara teknis, ditulisan ini kita khusus membahas tentang bagaimana berurusan dengan larik menggunakan paradigma fungsional.

Sebagai catatan, sumber data yang digunakan merujuk kepada tabel yang sudah ditampilkan diatas

Mapping

Mapping adalah tentang pemetaan, pernah mendapatkan kasus "Dari kumpulan data ini, saya hanya ingin mendapatkan data Nama, Jabatan, dan Status nya saja" alias menjadi seperti ini:

Nama Jabatan Status
Adam Software Engineer Karyawan Tetap
Idris Product Manager Karyawan Tetap
David HR Generalist Karyawan Tetap
Dewi UX Engineer Karyawan Tetap
Adit Content Writer Probation
Deya Engineering Manager Karyawan Tetap

Secara imperatif, langkah-langkah yang harus dilakukan adalah:

  1. Membuat variabel penampung berbentuk larik untuk data yang diinginkan
  2. Mengetahui jumlah data yang ada
  3. Membuat perulangan sebanyak jumlah data yang ada
  4. Ambil bagian data yang diinginkan, dan simpan ke variable penampung tersebut

Yang kurang lebih seperti ini:

// 1. Membuat variabel penampung berbentuk larik untuk data yang diinginkan
let dataKaryawanYangDiinginkan = [];

// 2. Mengetahui jumlah data yang ada
const totalDataKaryawan = dataKaryawan.length;

// 3. Membuat perulangan sebanyak jumlah data yang ada
for (let idx = 0; idx < totalDataKaryawan; idx++) {

  // 4. Ambil bagian data yang diinginkan
  // dan simpan ke variable penampung tersebut
  dataKaryawanYangDiinginkan.push({
    Nama: dataKaryawan[idx]['Nama'],
    Jabatan: dataKaryawan[idx]['Jabatan'],
    Status: dataKaryawan[idx]['Status']
  })
}

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Di JavaScript (per ECMA-262) tipe data larik sudah memiliki fungsi yang bernama map, yang salah satunya berguna untuk melakukan apa yang kita lakukan secara imperatif diatas:

const dataKaryawanYangDiinginkan = dataKaryawan.map(data => ({
  Nama: data["Nama"],
  Jabatan: data["Jabatan"],
  Status: data["Status"]
}))

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Kode diatas memiliki sintaks seperti ini:

[].map(function callback(currentValue[,index[,array]]) {
    // return element for new_array
}[,thisArg])

Sintaks sebelumnya, jika kita menggunakan kode JavaScript yang tidak terlalu modern-modern banget, kurang lebih seperti ini:

const dataKaryawanYangDiinginkan = dataKaryawan.map(function(data) {
  return {
    Nama: data["Nama"],
    Jabatan: data["Jabatan"],
    Status: data["Status"]
  }
})

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Yang jika kamu membutuhkan data indeks nya, kamu bisa menggunakan sintaks seperti ini:

dataKaryawan.map(function(data, index) {

Atau jika kamu sudah mengetahui bagian mana yang ingin diambil, larik yang adai di currentValue bisa kamu desctruct sehingga kode nya menjadi seperti ini:

dataKaryawan.map(function({ Nama, Jabatan, Status }, index) {

Karena kita tidak membutuhkan index, kode yang sudah kita buat bisa disederhanakan menjadi seperti ini:

const dataKaryawanYangDiinginkan = dataKaryawan.map(({
  Nama,
  Jabatan,
  Status
})) => ({
  Nama,
  Jabatan,
  Status
})

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Yang terlihat kurang enak dibaca dari yang sebelumnya hahaha.

Oke, map pada dasarnya membuat larik baru berdasarkan hasil iterasi terhadap elemen yang ada di larik yang sebelumnya tersebut.

Yang sederhananya, larik sebelumnya tidak termutasi, karena sebagaimana tujuan dari paradigma fungsional yang salah satunya adalah untuk menghindari mutasi state yang ada.

Filtering

Filtering adalah tentang penyaringan, pernah mendapatkan kasus "Dari kumpulan data ini, saya hanya ingin mendapatkan data karyawan yang tahun masuknya sebelum 2020" alias menjadi seperti ini:

Nama Jabatan Divisi Tahun Masuk Status
Adam Software Engineer Engineering 2019 Karyawan Tetap
David HR Generalist People 2019 Karyawan Tetap
Dewi UX Engineer Product 2019 Karyawan Tetap
Deya Engineering Manager Engineering 2019 Karyawan Tetap

Secara imperatif, langkah-langkah yang harus dilakukan adalah:

  1. Membuat variabel penampung berbentuk larik untuk data yang diinginkan
  2. Mengetahui jumlah data yang ada
  3. Membuat perulangan sebanyak jumlah data yang ada
  4. Jika memenuhi kondisi, ambil bagian data yang diinginkan, dan simpan ke variable penampung tersebut

Yang kurang lebih seperti ini:

// 1. Membuat variabel penampung berbentuk larik untuk data yang diinginkan
let dataKaryawanYangDiinginkan = [];

// 2. Mengetahui jumlah data yang ada
const totalDataKaryawan = dataKaryawan.length;

// 3. Membuat perulangan sebanyak jumlah data yang ada
for (let idx = 0; idx < totalDataKaryawan; idx++) {

  // 4. Jika memenuhi kondisi
  if (dataKaryawan[idx]['Tahun Masuk'] < 2020) {

    // ambil bagian data yang diinginkan
    // dan simpan ke variable penampung tersebut
    dataKaryawanYangDiinginkan.push(dataKaryawan[idx])
  }
}

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Divisi": "Engineering",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Di JavaScript (per ECMA-262) tipe data larik sudah memiliki fungsi yang bernama filter, yang salah satunya berguna untuk melakukan apa yang kita lakukan secara imperatif diatas:

const dataKaryawanYangDiinginkan = dataKaryawan.filter(
  data => data["Tahun Masuk"] < 2019
)

console.log(dataKaryawanYangDiinginkan) // keluaran:

[
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Divisi": "Engineering",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  },
  {...}, {...}, {...}
]

Kode diatas memiliki sintaks seperti ini:

[].filter(function callback(element[,index,[array]])[,thisArg])

Sintaks sebelumnya, jika kita menggunakan kode JavaScript yang tidak terlalu modern-modern banget, kurang lebih seperti ini:

const dataKaryawanYangDiinginkan = dataKaryawan.filter(function(data) {
  return data["Tahun Masuk"] < 2020
})

Yang jika kamu membutuhkan data indeks nya, kamu bisa menggunakan sintaks seperti ini:

dataKaryawan.filter(function(data, index) {

Karena objek tersebut tidak sesuai standar (memiliki whitespace) jadi kita tidak bisa destruct objek tersebut sebagaimana yang sebelumnya kita lakukan di proses pemetaan.

Di map pun sebenarnya kita bisa saja melakukan hal serupa, namun menggunakan filter lebih idiomatic karena larik baru dibuat berdasarkan elemen-elemen/data yang ada berhasil melewati "proses penyaringan".

Dan, ya, di filter pun membuat larik baru daripada memutasi larik yang ada.

Reducing

Ini mungkin konsep yang paling ribet dari iterasi ini, dan ingin menggunakan arti dari reduce (mengurangi) pun sepertinya kurang relevan.

Jika melihat-lihat tutorial di internet, yang membahas tentang reduce tidak jauh dari operasional aritmatika terhadap angka yang disimpan di larik.

Reduce ini biasa digunakan untuk menormalisasi data, flatten array, menikung teman, dan memberantas korupsi.

Oke oke bercanda.

Berdasarkan data diatas—setelah mikir keras—kita bisa menggunakan reduce untuk membuat laporan akan jumlah Status Karyawan dan Jumlah Divisi yang anggap aja total karyawan di PT. Mencari Cinta Sejati adalah 3,1337 karyawan untuk menentukan perlunya pembuatan departemen.

Jadi, data yang diinginkan adalah seperti ini:

Total Divisi Total Karyawan Tetap Total Karyawan Probation
4 5 1

Data diatas sudah menjadi objek, yang dengan struktur kurang lebih seperti ini:

{
  "Total Divisi": 4,
  "Total Karyawan Tetap": 5,
  "Total Karyawan Probation": 1
}

Cara imperatif untuk membuat data tersebut adalah:

  1. Membuat variabel penampung untuk menampung data-data tersebut
  2. Mengetahui jumlah data yang ada
  3. Membuat perulangan sebanyak jumlah data yang ada
  4. Ambil daftar divisi (yang unik)
  5. Ambil total data yang memiliki status "Karyawan Tetap"
  6. Ambil total data yang memiliki status "Probation"
  7. Tampilkan data final berdasarkan data yang ada di penampung

Yang kurang lebih contohnya adalah seperti ini:

// 1. Membuat variabel penampung untuk menampung data-data tersebut
let daftarDivisiUnik = [];
let totalKaryawanTetap = 0;
let totalKaryawanProbation = 0;

// 2. Mengetahui jumlah data yang ada
const totalDataKaryawan = dataKaryawan.length;

// 3. Membuat perulangan sebanyak jumlah data yang ada
for (let i = 0; i < totalDataKaryawan; i++) {

  // 4. Ambil daftar divisi (yang unik)
  if (daftarDivisiUnik.indexOf(dataKaryawan[i]["Divisi"]) === -1) {
    daftarDivisiUnik.push(dataKaryawan[i]["Divisi"])
  }

  // 5. Ambil total data yang memiliki status "Karyawan Tetap"
  if (dataKaryawan[i]["Status"] === "Karyawan Tetap") {
    totalKaryawanTetap++
  }

  // 6. Ambil total data yang memiliki status "Probation"
  if (dataKaryawan[i]["Status"] === "Probation") {
    totalKaryawanProbation++;
  }
}

// 7. Tampilkan data final berdasarkan data yang ada di penampung
const dataFinal = {
  "Total Divisi": daftarDivisiUnik.length,
  "Total Karyawan Tetap": totalKaryawanTetap,
  "Total Karyawan Probation": totalKaryawanProbation
}

console.log(dataFinal) // keluaran:

{
  "Total Divisi": 4,
  "Total Karyawan Tetap": 5,
  "Total Karyawan Probation": 1
}

Lumayan ribet yang penting berjalan!

Apakah bisa kita ehm sederhanakan menggunakan reduce?

const dataPreFinal = dataKaryawan.reduce((acc, curr) => {
  if (curr["Status"] === "Probation") {
    acc["karyawan_probation"]++
  }

  if (curr["Status"] === "Karyawan Tetap") {
    acc["karyawan_tetap"]++
  }

  if (acc["divisi_unik"].indexOf(curr["Divisi"]) === -1) {
    acc["divisi_unik"].push(curr["Divisi"])
  }

  return acc
}, {
  "karyawan_tetap": 0,
  "karyawan_probation": 0,
  "divisi_unik": []
})

const dataFinal = {
  "Total Divisi": dataPreFinal.divisi_unik.length,
  "Total Karyawan Tetap": dataPreFinal.karyawan_tetap,
  "Total Karyawan Probation": dataPreFinal.karyawan_probation
}

console.log(dataFinal) // keluaran:

{
  "Total Divisi": 4,
  "Total Karyawan Tetap": 5,
  "Total Karyawan Probation": 1
}

Oke sudah lebih sederhana!

Kita hanya membutuhkan 2 variabel untuk melakukan operasi tersebut.

...Yaaa walaupun ada si total divisi yang mengganggu...

Oke, reduce memiliki sintaks seperti berikut:

[].reduce(
  callback(accumulator,currentValue,[,index[,array]])
  [,initialValue]
)

Dan nilai kembaliannya adalah accumulator tersebut, berdasarkan kasus kita diatas, nilai accumulator nya bernilai berdasarkan dari initialValue, yakni:

  • "karyawan_tetap": 0,
  • "karyawan_probation": 0,
  • "divisi_unik": []

Nah currentValue atau curr (yang saya persingkat) ini adalah elemen yang sedang di proses, lihat dibaris tentang kita memastikan nilai Status apakah bernilai Probation atau Karyawan Tetap.

Di bagian yang menentukan divisi_unik ini sedikit menarik, karena yang kita pastikan itu adalah nilai dari acc["divisi_unik"], pertanyaannya, kenapa?

Pertama kali, nilai acc["divisi_unik"] itu sebuah larik kosong, jadi, elemen yang ada di acc["divisi_unik"] akan ditambahkan berdasarkan data yang diambil dari curr["Divisi"] pada iterasi terhadap elemen yang sedang berlangsung.

Selanjutnya, karena sekarang lariknya sudah memiliki elemen, jadi, baru melakukan penentuan apakah data dari curr["Divisi"] harus ditambahkan ke larik tersebut, dengan catatan, nilainya belum ada di acc["Divisi"].

Sudah jelas lah ya?

Jika ingin mendapatkan pembahasan lebih lanjut, bisa baca tulisan saya yang diterbitkan pada 13 Sep 2019 yang berjudul "Memahami Reduce di JavaScript" yang bisa dibaca disini.

Mungkin cara pembahasannya sedikit berbeda karena itu tulisan dari tahun lalu—terlebih tulisan pertama di blog ini!—😉

Potongan Kode

Agar tidak kehilangan fokus, berikut potongan kode yang digunakan di tulisan ini.

Data yang ingin diproses:

const dataKaryawan = [
  {
    "Nama": "Adam",
    "Jabatan": "Software Engineer",
    "Divisi": "Engineering",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  },
  {
    "Nama": "Idris",
    "Jabatan": "Product Manager",
    "Divisi": "Product",
    "Tahun Masuk": 2020,
    "Status": "Karyawan Tetap"
  },
  {
    "Nama": "David",
    "Jabatan": "HR Generalist",
    "Divisi": "People",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  },
  {
    "Nama": "Dewi",
    "Jabatan": "UX Engineer",
    "Divisi": "Product",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  },
  {
    "Nama": "Adit",
    "Jabatan": "Content Writer",
    "Divisi": "Marketing",
    "Tahun Masuk": 2020,
    "Status": "Probation"
  },
  {
    "Nama": "Deya",
    "Jabatan": "Engineering Manager",
    "Divisi": "Engineering",
    "Tahun Masuk": 2019,
    "Status": "Karyawan Tetap"
  }
]

Data hasil mapping:

const dataKaryawanYangDiinginkan = dataKaryawan.map(data => ({
  Nama: data["Nama"],
  Jabatan: data["Jabatan"],
  Status: data["Status"]
}))

Data hasil filtering:

const dataKaryawanYangDiinginkan = dataKaryawan.filter(
  data => data["Tahun Masuk"] < 2019
)

Data hasil reducing:

const dataPreFinal = dataKaryawan.reduce((acc, curr) => {
  if (curr["Status"] === "Probation") {
    acc["karyawan_probation"]++
  }

  if (curr["Status"] === "Karyawan Tetap") {
    acc["karyawan_tetap"]++
  }

  if (acc["divisi_unik"].indexOf(curr["Divisi"]) === -1) {
    acc["divisi_unik"].push(curr["Divisi"])
  }

  return acc
}, {
  "karyawan_tetap": 0,
  "karyawan_probation": 0,
  "divisi_unik": []
})

const dataFinal = {
  "Total Divisi": dataPreFinal.divisi_unik.length,
  "Total Karyawan Tetap": dataPreFinal.karyawan_tetap,
  "Total Karyawan Probation": dataPreFinal.karyawan_probation
}

Silahkan coba-coba sendiri! Di Devtools Firefox juga menarik kok

Unduh Firefox Disini

Penutup

Selalu ada pro-kontra dalam sesuatu.

Seinget saya, tantangan dari paradigma fungsional ini adalah salah satunya di efisiensi proses kalkulasi.

Pada beberapa kasus, terkadang lebih efisien memutasi larik secara langsung daripada membuat salinannya/membuat larik baru berdasarkan larik sebelumnya.

Pada beberapa kasus, terkadang lebih efisien menghapus larik berdasarkan index nya secara langsung, daripada membuat salinannya/membuat larik baru berdasarkan larik sebelumnya yang tidak ingin disertakan.

Namun paradigma fungsional menurut saya bukan tentang menyelesaikan efek samping dari sebuah proses (seperti performa), melainkan tentang bagaimana melakukan sesuatu untuk proses tersebut.

Jika terus menyelam lebih dalam, para pemrogram "yang fungsional" cenderung menggunakan beberapa pendekatan yang berbeda, mungkin karena paradigma yang dia anut seperti meminimalisir side-effect dan memaksimalkan penggunaan pure function, mengalokasikan data yang tidak pernah diubah, dsb.

Dan yang paling kontras, tentang bagaimana dia melihat sesuatu dan cara ketika dia berhadapan dengan sesuatu tersebut.

Yang bisa disimpulkan seperti Tell me what, not tell me how.

Masih panjang perjalanan untuk mengarungi lautan paradigma fungsional ini, dan semoga tulisan ini menjadi kilometer pertama kamu untuk terus berlabuh lebih jauh meng-eksplorasi paradigma fungsional!

Selamat malam mingguan!