Budi menyiapkan promo kafe bertingkat: pelanggan member mendapat potongan sepuluh persen, dan voucher mingguan memotong lima persen lagi dari harga sisa. Diskon harus bisa dipasang dalam urutan apa pun, dipakai berulang untuk seluruh daftar menu, dan dirangkai dengan perhitungan pajak — tanpa menimpa data siapa pun. Semua konsep functional yang kita bangun sejauh ini mampu menangani tugas itu; yang kurang hanyalah kosa kata ringkas untuk menyebut pola-pola yang berulang.

Chapter ini memberi nama pada empat pola tersebut. Currying memecah function banyak argumen menjadi rangkaian panggilan satu argumen. Partial application mengunci sebagian argumen lebih awal, misalnya lewat bind. Point-free mengirim function yang sudah jadi tanpa membungkusnya lagi. Dan pipeline merangkai semuanya menjadi satu alur — pola yang kita bangun sendiri di chapter Function Composition.

Currying: Satu Argumen per Pemanggilan #

Bentuk diskon(persen)(harga) terlihat mencurigakan pada pandangan pertama — dua tanda kurung berurutan. Mekanismenya sederhana: diskon(10) mengembalikan function, dan function itulah yang langsung dipanggil dengan (25000). Function yang kembaliannya function lagi, argumen demi argumen, inilah currying:

// pola-fungsional.js
// Currying, partial application, point-free, dan pipeline.

// --- 1. currying: satu argumen per pemanggilan ---
function diskon(persen) {
  return function (harga) {
    return harga - (harga * persen) / 100;
  };
}

console.log(diskon(10)(25000));
console.log(diskon(25)(25000));

// --- 2. diskon bertingkat: function yang sudah terisi ---
const hargaMember = diskon(10);
const hargaVoucher = diskon(5);
console.log(hargaMember(25000));
console.log(hargaVoucher(22500));
console.log(hargaVoucher(hargaMember(25000)));

// --- 3. partial application dengan bind ---
function hitungPajak(persen, harga) {
  return harga + (harga * persen) / 100;
}

const pajakLayanan = hitungPajak.bind(null, 11);
console.log(pajakLayanan(25000));
console.log(pajakLayanan(18000));

// --- 4. point-free: function siap pakai dikirim langsung ---
const keranjang = [25000, 18000, 12000];
console.log(keranjang.map(hargaMember));

// --- 5. pipeline: pola yang merangkai semuanya ---
function pipe(...fns) {
  return function (nilaiAwal) {
    return fns.reduce((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const strukPembayaran = pipe(hargaMember, pajakLayanan);
console.log(strukPembayaran(25000));
console.log(strukPembayaran(18000));

Output yang Dihasilkan:

text
22500
18750
22500
21375
21375
27750
19980
[ 22500, 16200, 10800 ]
24975
17982

Mari kita telaah baris-baris pentingnya:

  1. Baris 5–9 (diskon): function luar menerima persen, function dalam menerima harga, dan function dalam menutup nilai persen — mekanisme closure yang sama dengan builder di chapter Higher-Order Functions. Rumus harga - (harga * persen) / 100 menjaga hasil tetap bilangan bulat.
  2. Baris 11–12 (panggilan bertingkat): diskon(10)(25000) menghitung 25000 - 2500 dan menghasilkan 22500; diskon(25)(25000) menghasilkan 18750. Dua tanda kurung itu adalah dua pemanggilan berturut-turut.
  3. Baris 15–16 (function yang sudah terisi): hargaMember dan hargaVoucher menyimpan versi diskon dengan tarif yang terpasang. Nama-nama inilah yang membuat baris pemanggilan di bawahnya terbaca seperti aturan bisnis, bukan angka mentah.
  4. Baris 17–19 (diskon bertingkat): hargaMember(25000) menghasilkan 22500, lalu hargaVoucher(22500) memotong lima persen dari harga sisa dan menghasilkan 21375. Baris 19 menyusun keduanya sekali jalan — hasil member menjadi masukan voucher.
  5. Baris 22–28 (partial application): hitungPajak ditulis sebagai function biasa dengan dua parameter. hitungPajak.bind(null, 11) membuat function baru dengan persen terkunci di angka 11; null hanyalah pengganti this yang tidak kita pakai. pajakLayanan(25000) menghitung 25000 + 2750 dan menghasilkan 27750.
  6. Baris 32 (point-free): keranjang.map(hargaMember) mengirim function yang sudah jadi, tanpa membungkus => hargaMember(harga). Hasilnya [ 22500, 16200, 10800 ] — diskon member diterapkan ke seluruh keranjang.
  7. Baris 35–39 (pipe): builder pipeline dari chapter Function Composition, dipakai kembali apa adanya.
  8. Baris 41–43 (pipeline dua tahap): strukPembayaran(25000) mengalirkan 25000 menjadi 22500 lewat diskon, lalu menjadi 24975 lewat pajak 11 persen. Dua pola — currying dan partial application — bekerja berdampingan dalam satu alur.

Currying dan Partial Application: Apa Bedanya #

Keduanya sering tertukar karena hasil terakhirnya mirip: sebuah function dengan sebagian argumen yang sudah terpasang. Perbedaannya ada pada bentuk asalnya:

AspekCurryingPartial application
Bentuk functionditulis bertingkat sejak awal — f(a)(b)ditulis biasa — f(a, b)
Cara mengunci argumenstruktur functionnya sendiribind atau pembungkus arrow
Cocok untukkonsep bertahap yang memang satu-per-satufunction lama yang ingin dipakai parsial

diskon adalah currying karena bentuknya memang f(a)(b) — tarif dulu, harga kemudian. hitungPajak adalah partial application karena kita mengunci salah satu parameternya dari luar, tanpa mengubah penulisannya. bind bukan satu-satunya jalan: arrow function satu baris seperti (harga) => hitungPajak(11, harga) menghasilkan perilaku yang sama.

Bentuk currying juga lazim ditulis dengan arrow function beruntun, tanpa kata function sama sekali. Kedua penulisan ini identik perilakunya:

js
const potongan = (persen) => (harga) => harga - (harga * persen) / 100;
console.log(potongan(15)(20000));

Output yang Dihasilkan:

text
17000

Dibaca dari kiri ke kanan: potongan menerima persen, lalu mengembalikan function yang menerima harga. Pemilihan bentuk menjadi selera tim — bentuk function lebih terbuka bagi pembaca pemula, sedangkan arrow beruntun lebih padat untuk function satu ekspresi.

Point-free dan Batasnya #

Gaya point-free — kadang disebut tacit — menghilangkan pemberian nama pada argumen yang hanya lewat saja: map(hargaMember) alih-alih map((harga) => hargaMember(harga)). Manfaatnya nyata pada rangkaian pendek: barisnya lebih padat, dan tidak ada nama yang menyesatkan. Batasnya juga nyata: bila callback butuh logika tambahan, parameter bernama tetap lebih jujur. Kaidah praktisnya — pakai point-free bila functionnya sudah ada dan namanya cukup menjelaskan.

Batas itu kritis karena callback map mengirim lebih dari satu argumen — nilai, index, dan array asal — sedangkan point-free meneruskan semuanya. Bila function penerimanya memakai argumen kedua untuk maksud lain, hasilnya meleset tanpa pesan galat apa pun. Contoh klasiknya parseInt, yang argumen keduanya adalah radix pembacaan angka:

js
console.log([10, 20, 30].map(parseInt));
console.log([10, 20, 30].map((nilai) => parseInt(nilai, 10)));

Output yang Dihasilkan:

text
[ 10, NaN, NaN ]
[ 10, 20, 30 ]

Pada baris pertama, parseInt menerima index sebagai radix: parseInt(20, 1) dan parseInt(30, 2) menghasilkan NaN. Baris kedua membungkus dengan parameter bernama yang mengunci radix 10 — pembungkus kecil yang menegaskan bahwa point-free boleh dipakai hanya bila bentuk function penerimanya cocok dengan bentuk argumen yang dikirim.

Pipeline: Pola yang Merangkai Semuanya #

Baris 41–43 menutup section ini dengan gambaran utuh. strukPembayaran menyusun function hasil currying (hargaMember) dan function hasil partial application (pajakLayanan) menjadi satu pipeline, dan pemanggilannya cukup menyebut harga awal. Kedua pola tadi persis menyediakan bahan yang dibutuhkan pipeline: function satu-masukan-satu-keluaran yang siap disambung.

Empat pola pada chapter ini — currying, partial application, point-free, pipeline — bukan fitur bahasa yang baru, melainkan konsekuensi dari satu keputusan desain JavaScript: function adalah nilai. Dengan function murni sebagai bahan, salinan data sebagai pembawa perubahan, dan pipeline sebagai alur, inti logika program bisa dibangun tanpa state yang saling menimpa. Keterampilan berikutnya melangkah keluar dari pemrosesan sinkron: section berikutnya, Asynchronous JavaScript, membahas alur program yang menunggu jawaban — dari timer sampai pengambilan data lewat jaringan.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

pola-fungsional.js

Unduh berkas
javascript
// pola-fungsional.js
// Currying, partial application, point-free, dan pipeline.

// --- 1. currying: satu argumen per pemanggilan ---
function diskon(persen) {
  return function (harga) {
    return harga - (harga * persen) / 100;
  };
}

console.log(diskon(10)(25000));
console.log(diskon(25)(25000));

// --- 2. diskon bertingkat: function yang sudah terisi ---
const hargaMember = diskon(10);
const hargaVoucher = diskon(5);
console.log(hargaMember(25000));
console.log(hargaVoucher(22500));
console.log(hargaVoucher(hargaMember(25000)));

// --- 3. partial application dengan bind ---
function hitungPajak(persen, harga) {
  return harga + (harga * persen) / 100;
}

const pajakLayanan = hitungPajak.bind(null, 11);
console.log(pajakLayanan(25000));
console.log(pajakLayanan(18000));

// --- 4. point-free: function siap pakai dikirim langsung ---
const keranjang = [25000, 18000, 12000];
console.log(keranjang.map(hargaMember));

// --- 5. pipeline: pola yang merangkai semuanya ---
function pipe(...fns) {
  return function (nilaiAwal) {
    return fns.reduce((nilai, fn) => fn(nilai), nilaiAwal);
  };
}

const strukPembayaran = pipe(hargaMember, pajakLayanan);
console.log(strukPembayaran(25000));
console.log(strukPembayaran(18000));