Pada Chapter First-Class Functions kita sudah melihat bahwa function bisa disimpan di variabel, dikirim sebagai argument, dan dikembalikan dari function lain. Melihat fakta itu sebagai trik saja akan sayang — kemampuan tersebut adalah pintu masuk ke pola desain yang membuat kode mudah dikembangkan tanpa dirombak. Pada chapter ini kita memanfaatkan pola itu secara sadar lewat dua teknik utama: strategy pattern dan dispatch dictionary.
Definisi dan Posisinya #
Higher-order function adalah function yang melakukan salah satu dari dua hal berikut:
- menerima satu atau lebih function sebagai argument, atau
- mengembalikan function sebagai nilai balik.
Contoh pertama sudah sering kita pakai tanpa menyadarinya: sorted(data, key=...) menerima function key, dan method list.sort(key=...) bekerja dengan cara yang sama. Contoh kedua kita jumpai pada function pembuat seperti buat_pangkat(2) yang mengembalikan function siap pakai.
Pola Strategy: Algoritma Dikirim dari Luar #
Masalah umum: satu proses inti, banyak variasi cara. Misalkan program kasir harus menampilkan harga dalam dua format — rupiah lengkap dan ringkas. Daripada menanam pilihan format di dalam badan function, kita kumpulkan setiap format sebagai function mandiri lalu memilihnya dari dictionary:
def format_rupiah(nominal):
return f"Rp{nominal:,}".replace(",", ".")
def format_ringkas(nominal):
return f"{nominal // 1000}rb"
format_strategi = {
"lengkap": format_rupiah,
"ringkas": format_ringkas,
}
def tampilkan_harga(nominal, gaya):
print(format_strategi[gaya](nominal))
tampilkan_harga(1500000, "lengkap")
tampilkan_harga(1500000, "ringkas")Output yang Dihasilkan:
Rp1.500.000
1500rbMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 1-5 (
def format_rupiahdandef format_ringkas): setiap algoritma format berdiri sendiri sebagai function kecil dan bisa diuji terpisah. - Baris 7-10 (
format_strategi = {...}): dictionary menyimpan object function sebagai value — bukan hasil pemanggilannya, karena tidak ada tanda kurung setelah nama function. - Baris 12-13 (
def tampilkan_harga): proses inti hanya melakukan lookupformat_strategi[gaya]lalu memanggilnya. Function ini tidak perlu tahu detail format apapun. - Baris 15-16: pemanggil memilih strategi lewat string; menambah gaya baru cukup menambah satu entry dictionary tanpa menyentuh badan
tampilkan_harga.
Dispatch Dictionary vs Rantai if-elif #
Bentuk klasik pemetaan kode ke aksi adalah rantai if-elif. Bandingkan kedua implementasi status pesanan berikut:
def status_if(kode):
if kode == "P":
return "Pesanan diproses"
elif kode == "K":
return "Pesanan dikirim"
elif kode == "S":
return "Pesanan selesai"
else:
return "Kode tidak dikenal"
STATUS = {
"P": "Pesanan diproses",
"K": "Pesanan dikirim",
"S": "Pesanan selesai",
}
def status_dict(kode):
return STATUS.get(kode, "Kode tidak dikenal")
for kode in ["P", "K", "S", "X"]:
print(f"{kode}: if -> {status_if(kode)} | dict -> {status_dict(kode)}")Output yang Dihasilkan:
P: if -> Pesanan diproses | dict -> Pesanan diproses
K: if -> Pesanan dikirim | dict -> Pesanan dikirim
S: if -> Pesanan selesai | dict -> Pesanan selesai
X: if -> Kode tidak dikenal | dict -> Kode tidak dikenalKedua implementasi menghasilkan output identik untuk semua kode, termasuk kode tak dikenal X. Perbedaannya baru terasa saat program berkembang:
| Aspek | Rantai if-elif | Dispatch dictionary |
|---|---|---|
| Menambah kasus baru | Edit badan function — setiap edit berisiko merusak cabang lain | Tambah satu entry dictionary |
| Panjang function | Tumbuh linear mengikuti jumlah kasus | Tetap satu baris |
| Cara lookup | Dievaluasi berurutan dari cabang pertama | Satu kali lookup hash |
| Cocok untuk | Logika bercabang dengan kondisi beragam (rentang, gabungan) | Pemetaan langsung key ke nilai atau function |
Aturan praktisnya: ketika setiap cabang hanya berupa pemetaan kode → aksi, dispatch dictionary menang tipis dan semakin jauh semakin menang. Ketika cabang memuat kondisi kompleks seperti rentang usia atau kombinasi flag, if-elif tetap wajar dipakai.
Function Factory: Validator Sesuai Pesanan #
Higher-order function kedua jenisnya — yang mengembalikan function — sangat cocok memproduksi validator yang aturannya diatur saat pabrik dipanggil:
def buat_validasi_panjang(batas_min, pesan):
def validasi(teks):
if len(teks) < batas_min:
return f"Tidak valid: {pesan}"
return "Valid"
return validasi
validasi_email = buat_validasi_panjang(3, "email terlalu pendek")
validasi_password = buat_validasi_panjang(8, "password minimal 8 karakter")
print(validasi_email("[email protected]"))
print(validasi_password("kopi"))Output yang Dihasilkan:
Valid
Tidak valid: password minimal 8 karakterSatu cetakan buat_validasi_panjang menghasilkan dua validator dengan ambang dan pesan berbeda. Inner function validasi membaca batas_min dan pesan dari lingkungan function pembungkusnya — mekanisme pengambilan variabel lintingkungan ini bernama closure dan menjadi fokus Chapter Closures.