Bayangkan fungsi yang perilakunya selalu bisa diprediksi: masukan yang sama selalu menghasilkan keluaran yang sama, dan sekali dipanggil fungsi tersebut tidak mengubah apa pun di luar dirinya. Fungsi seperti itu disebut pure function, dan gagasan sederhana ini adalah fondasi utama gaya pemrograman fungsional. Di PHP, pure function bukan fitur yang perlu diaktifkan, melainkan disiplin dalam mendesain fungsi. Pada chapter ini kita akan membedah dua syarat kemurnian sebuah fungsi, mengenali bentuk-bentuk kode yang melanggarnya, lalu melihat strategi memisahkan kode murni dari kode yang memang harus menyentuh dunia luar.
Dua Syarat Fungsi Murni #
Sebuah fungsi disebut murni apabila memenuhi dua syarat sekaligus. Pertama, fungsi tersebut deterministik: untuk masukan yang sama, keluaran yang dihasilkan selalu sama, kapan pun fungsi dipanggil. Kedua, fungsi tersebut bebas side effect: pemanggilan fungsi tidak mengubah keadaan apa pun di luar tubuhnya — tidak menulis ke layar, tidak menggeser variabel global, dan tidak menyentuh berkas maupun database.
Bentuk paling sederhananya memang tampak seperti fungsi biasa:
<?php
function hitungDiskon(float $harga, float $persen): float
{
return $harga * $persen / 100;
}
$hasilPertama = hitungDiskon(200000, 25);
$hasilKedua = hitungDiskon(200000, 25);
echo $hasilPertama . "\n";
echo $hasilKedua . "\n";Output yang Dihasilkan:
50000
50000Mari kita telaah kode di atas:
hitungDiskon()hanya membaca dua parameternya, mengalikan keduanya, lalu mengembalikan hasil pembagian — tidak ada satu pun pernyataan yang menyentuh dunia luar.$hasilPertamadan$hasilKeduadiisi lewat pemanggilan dengan argumen yang persis sama, sehingga keduanya pasti berisi50000.- Setelah kedua pemanggilan tersebut, tidak ada keadaan program yang berubah — kita boleh memanggil fungsi ini seratus kali dan hasilnya tetap sama.
Kemurnian seperti inilah yang membuat fungsi terasa seperti rumus matematika: hitungDiskon(200000, 25) bisa digantikan nilainya (50000) tanpa mengubah makna program.
Mengenali Function yang Tidak Murni #
Kebalikan pure function adalah impure function — fungsi yang hasilnya dipengaruhi keadaan luar, atau yang mengubah keadaan luar saat dijalankan. Bentuk pelanggarannya bermacam-macam:
| Bentuk kode | Alasan membuat fungsi impure |
|---|---|
echo, print | Menulis ke output — efek terasa di luar fungsi |
Mutasi variabel global atau static | Mengubah status yang bertahan antar pemanggilan |
date(), time(), random_int() | Hasil bergantung pada keadaan runtime |
| Baca/tulis berkas, query database | Dunia luar bisa berubah tanpa sepengetahuan fungsi |
Dua bentuk pertama paling sering muncul di kode sehari-hari. Perhatikan fungsi berikut yang melanggar kedua syarat sekaligus:
<?php
$jumlahLog = 0;
function catatAktivitas(string $pesan): void
{
global $jumlahLog;
$jumlahLog = $jumlahLog + 1;
echo "[log] $pesan\n";
}
catatAktivitas('Pengguna login');
catatAktivitas('Pengguna logout');
echo "Jumlah log: $jumlahLog\n";Output yang Dihasilkan:
[log] Pengguna login
[log] Pengguna logout
Jumlah log: 2Fungsi catatAktivitas() mengubah variabel global $jumlahLog (side effect), dan perilakunya berbeda-beda tergantung berapa kali fungsi tersebut sudah dipanggil sebelumnya (tidak deterministik). Pemanggil tidak bisa mengetahui akibat sebuah pemanggilan hanya dengan membaca argumennya; pemanggil wajib menelusuri juga riwayat program. Variabel static menimbulkan masalah yang serupa:
<?php
function nomorTiketBerikutnya(): string
{
static $nomor = 0;
$nomor = $nomor + 1;
return sprintf('TKT-%03d', $nomor);
}
echo nomorTiketBerikutnya() . "\n";
echo nomorTiketBerikutnya() . "\n";Output yang Dihasilkan:
TKT-001
TKT-002Fungsi nomorTiketBerikutnya() tidak menerima argumen sama sekali, tetapi menghasilkan TKT-001 lalu TKT-002 — nilai kembalinya bergantung pada hitungan internal yang tersimpan di dalam fungsi. Pola seperti ini sah-sah saja untuk kebutuhan penomoran, tetapi konsekuensinya fungsi tersebut sulit diuji secara terisolasi dan tidak aman dipanggil dari beberapa proses yang berjalan bersamaan.
Bentuk ketiga menyangkut waktu. Fungsi yang memanggil date() membaca jam server, sehingga hasilnya bisa berbeda antara pagi dan siang hari:
<?php
// Impure: hasilnya bergantung pada jam server saat fungsi dipanggil.
function sapaSesuaiJamServer(string $nama): string
{
$jam = (int) date('G');
return $jam < 12 ? "Selamat pagi, $nama" : "Selamat siang, $nama";
}
// Pure: jam datang sebagai argumen sehingga hasilnya pasti.
function sapaSesuaiJam(string $nama, int $jam): string
{
return $jam < 12 ? "Selamat pagi, $nama" : "Selamat siang, $nama";
}
echo sapaSesuaiJam('Budi', 9) . "\n";
echo sapaSesuaiJam('Budi', 14) . "\n";Output yang Dihasilkan:
Selamat pagi, Budi
Selamat siang, BudiPerhatikan bahwa sapaSesuaiJamServer() hanya didefinisikan dan tidak pernah dipanggil pada contoh di atas, sehingga keluaran blok tetap pasti. Versi murni di bawahnya mengambil sumber ketidakpastian — jam — lalu menjadikannya parameter. Teknik “angkat ketergantungan menjadi argumen” ini adalah pola refactor yang paling sering dipakai untuk memurnikan fungsi, dan kita akan bertemu pola yang sama berulang kali di sepanjang section ini.
Manfaat bagi Pengujian dan Lebih dari Itu #
Kemurnian membayar dirinya sendiri dalam tiga bentuk praktis. Pertama, testability: pengujian untuk pure function tidak butuh persiapan maupun pembersihan keadaan — cukup panggil fungsi lalu bandingkan hasilnya dengan nilai yang diharapkan, dan pengujian tersebut tetap hijau jam berapa pun dijalankan. Kedua, memoization: karena masukan yang sama pasti menghasilkan keluaran yang sama, hasil pemanggilan lama boleh disimpan dalam cache dan dipakai ulang tanpa takut basi — kita akan membangun mekanisme ini pada Chapter Closures . Ketiga, keamanan eksekusi paralel: fungsi yang tidak membagi status bersama tidak akan saling menimpa hasil ketika dijalankan bersamaan, sesuatu yang makin relevan saat data diproses oleh beberapa worker sekaligus.
Functional Core, Imperative Shell #
Sampai di titik ini mungkin terasa seolah seluruh program harus murni — padahal program yang berguna justru hidup dari side effect: mencetak ke layar, menyimpan ke database, mengirim email. Strategi yang dikenal sebagai functional core, imperative shell menawarkan komprominya: kumpulkan aturan dan perhitungan penting sebagai pure function di inti (core), lalu biarkan lapisan tipis di tepi (shell) melakukan seluruh I/O — membaca masukan, memanggil inti, menampilkan keluaran.
<?php
// Inti fungsional: aturan bisnis sebagai pure function.
function totalBelanja(array $itemBelanja): int
{
return array_sum(array_map(
fn (array $item): int => $item['harga'] * $item['jumlah'],
$itemBelanja
));
}
function hargaSetelahPromoMember(int $total, bool $member): float
{
return $member ? $total * 0.9 : (float) $total;
}
// Cangkang imperatif: menyiapkan data konkret dan mencetak hasil.
$itemBelanja = [
['nama' => 'Kopi Gayo', 'harga' => 85000, 'jumlah' => 2],
['nama' => 'Gula Aren', 'harga' => 25000, 'jumlah' => 4],
];
$adalahMember = true;
$total = totalBelanja($itemBelanja);
echo 'Total: Rp' . hargaSetelahPromoMember($total, $adalahMember) . "\n";Output yang Dihasilkan:
Total: Rp243000Mari kita bedah pembagian perannya:
totalBelanja()meringkas array item menjadi satu angka lewatarray_map()laluarray_sum()— murni karena hanya membaca argumennya.hargaSetelahPromoMember()menerapkan aturan promo pada sebuah angka — juga murni, tanpa peduli dari mana angka itu berasal.- Tujuh baris terakhir adalah cangkang: menyiapkan data konkret, menetapkan status member, lalu
echohasil akhir ke layar.
Dengan struktur seperti ini, seluruh aturan bisnis (inti) dapat diuji tanpa terminal, jam, maupun database, sementara bagian yang sulit diuji (cangkang) menyusut menjadi beberapa baris tipis yang mudah diperiksa mata.
Ringkasan #
Pure function memenuhi dua syarat: deterministik terhadap masukannya, dan tidak menimbulkan side effect. echo, mutasi global/static, dan pembacaan waktu acak adalah contoh pelanggar yang paling sering kita temui. Impurity tidak mungkin dihapus total dari aplikasi nyata, tetapi bisa didorong ke tepi agar inti program tetap terprediksi. Prediksi fungsi baru benar-benar lengkap ketika data yang dioper juga tidak berubah diam-diam — kebiasaan itu bernama immutability, topik chapter berikutnya.