Program sering kali bukan satu perhitungan besar, melainkan rangkaian langkah kecil: bersihkan input, validasi, lalu bentuk output. Menyusun langkah-langkah tersebut sebagai function terpisah mudah — yang sulit adalah merangkainya kembali tanpa kehilangan keterbacaan. Pemanggilan bersarang seperti transform(validasi(parse(baris))) harus dibaca dari dalam ke luar, dan makin panjang makin melelahkan. Function composition menyelesaikan masalah penggabungan ini.
compose: Menyusun Dua Function #
Matematika menuliskan komposisi sebagai f ∘ g, yang berarti “jalankan g dulu, lalu f”. Dalam Python, helper-nya ringkas:
def compose(f, g):
return lambda x: f(g(x))
def normalisasi(teks):
return teks.strip().lower()
def beri_domain(lokal):
return lokal + "@example.com"
email_dari_nama = compose(beri_domain, normalisasi)
print(email_dari_nama(" Budi.Santoso "))Output yang Dihasilkan:
Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1-2 (
def compose):composeadalah higher-order function — menerima dua function dan mengembalikan function baru berbentuklambda x: f(g(x)). - Baris 10 (
email_dari_nama = compose(beri_domain, normalisasi)): argument kedua (normalisasi) berjalan lebih dulu, hasilnya diteruskan ke argument pertama (beri_domain). - Baris 12: string ber-spasi di-
stripdan di-lowercase menjadibudi.santoso, lalu domain@example.comdilekatkan.
Urutan baca “kanan ke kiri” ini sah, tetapi banyak orang menemukan urutan kiri ke kanan lebih natural untuk aliran data. Kendalanya makin terasa saat tahap bertambah: transform(validasi(normalisasi(bersihkan(baris)))) memaksa mata melompat ke bagian terdalam lebih dulu, lalu mundur keluar satu per satu — padahal data mengalir ke arah sebaliknya.
pipe: Membaca Kiri ke Kanan #
Helper pipe menerima function sebanyak apa pun, lalu mengalirkan nilai melewatinya satu per satu dari kiri ke kanan. Bahasa seperti Elixir dan F# bahkan menyediakan operator |> untuk gaya ini; Python belum memilikinya, tetapi helper tujuh baris berikut memberi rasa yang sama:
def pipe(*fungsi):
def jalankan(nilai):
for f in fungsi:
nilai = f(nilai)
return nilai
return jalankan
def pecah_baris(teks):
return teks.split(";")
def ambil_angka(pasangan):
return [int(p.split("=")[1]) for p in pasangan]
def jumlahkan(angka):
return sum(angka)
def ke_rupiah(nominal):
return f"Rp{nominal:,}"
olah_pesanan = pipe(pecah_baris, ambil_angka, jumlahkan, ke_rupiah)
print(olah_pesanan("kopi=15000;gula=12000;teh=25000"))Output yang Dihasilkan:
Rp52,000Baris olah_pesanan = pipe(pecah_baris, ambil_angka, jumlahkan, ke_rupiah) kini terbaca seperti resep: pecah baris, ambil angka, jumlahkan, format menjadi rupiah. Perhatikan bahwa setiap tahap harus cocok — keluaran pecah_baris (list of string) menjadi masukan ambil_angka. pipe sendiri adalah closure: inner function jalankan menangkap tuple fungsi dari lingkungan pembungkusnya.
Partial Application dengan functools.partial #
Cara ketiga menyusun adalah mengkhususkan function yang sudah ada. functools.partial membekukan sebagian argument dan mengembalikan function baru dengan argument tersisa:
from functools import partial
def potongan(harga, persen):
return round(harga * (1 - persen / 100))
potongan_member = partial(potongan, persen=10)
potongan_staff = partial(potongan, persen=30)
print(potongan_member(200000))
print(potongan_staff(200000))Output yang Dihasilkan:
180000
140000Function potongan umum menerima dua parameter. Dari satu cetakan itu, partial menghasilkan potongan_member yang selalu memakai persen=10 dan potongan_staff dengan persen=30 — pemanggil tinggal menyuplai harga. Nilai yang dibekukan boleh positional maupun keyword; sisanya diisi saat function hasil partial dipanggil. Bedakan tugasnya dengan compose: partial mengkhususkan satu function; compose menggabungkan beberapa function.
Pipeline Nyata: parse → validate → transform #
Mari kita rangkai semuanya untuk kasus pengolahan data pegawai — membaca baris mentah, membuang data tidak valid, lalu membentuk teks siap tampil:
def parse(baris):
nama, umur = baris.strip().split(",")
return {"nama": nama, "umur": int(umur)}
def validasi(pegawai):
return 18 <= pegawai["umur"] <= 60
def transform(pegawai):
return f"{pegawai['nama'].title()} ({pegawai['umur']} tahun)"
data_mentah = ["budi santoso,29", "siti rahayu,31", "ani lestari,17"]
hasil = [transform(p) for p in map(parse, data_mentah) if validasi(p)]
print(hasil)Output yang Dihasilkan:
['Budi Santoso (29 tahun)', 'Siti Rahayu (31 tahun)']Aliran datanya terbaca dari satu baris terakhir: map(parse, data_mentah) mengubah setiap baris menjadi dictionary, validasi(p) menyaring yang berumur di luar 18-60 (Ani yang berumur 17 tersingkir), dan transform(p) membentuk string final. Setiap tahap adalah function kecil yang bisa diuji terpisah — persis semangat pure function pada chapter sebelumnya.