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:

python
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:

  1. Baris 1-2 (def compose): compose adalah higher-order function — menerima dua function dan mengembalikan function baru berbentuk lambda x: f(g(x)).
  2. Baris 10 (email_dari_nama = compose(beri_domain, normalisasi)): argument kedua (normalisasi) berjalan lebih dulu, hasilnya diteruskan ke argument pertama (beri_domain).
  3. Baris 12: string ber-spasi di-strip dan di-lowercase menjadi budi.santoso, lalu domain @example.com dilekatkan.

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:

python
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:

text
Rp52,000

Baris 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:

python
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:

text
180000
140000

Function 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:

python
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:

text
['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.