Fungsi biasa menerima data dan mengembalikan data. Higher-order function naik satu tingkat: menerima fungsi sebagai argumen, mengembalikan fungsi sebagai hasil, atau melakukan keduanya sekaligus. Konsep ini mungkin terdengar akademis, padahal Anda sudah lama memakainya — setiap kali menulis callback untuk array_map() atau usort(), kita sedang mengoper fungsi ke higher-order function milik PHP. Setelah pure function dan immutability, chapter ini membahas mesin yang membuat keduanya praktis di kode sehari-hari.

Fungsi sebagai Argumen #

Bentuk pertama higher-order function adalah fungsi yang menerima fungsi lain lewat parameter bertipe callable. Supaya mekanismenya terlihat telanjang, mari kita buat versi sederhananya sendiri:

php
<?php

// Higher-order function: menerima fungsi sebagai argumen.
function terapkanPadaSetiap(array $nilai, callable $transformasi): array
{
    $hasil = [];
    foreach ($nilai as $satu) {
        $hasil[] = $transformasi($satu);
    }
    return $hasil;
}

$hargaNetto = [50000, 85000, 120000];

$hargaBruto = terapkanPadaSetiap(
    $hargaNetto,
    fn (int $harga): float => $harga * 1.11
);

echo implode(', ', $hargaBruto) . "\n";

Output yang Dihasilkan:

text
55500, 94350, 133200

Mari kita bedah perannya satu per satu:

  1. Parameter $transformasi bertipe callable menandakan bahwa fungsi ini menerima fungsi lain sebagai masukan — itulah yang menjadikannya higher-order.
  2. Baris $hasil[] = $transformasi($satu); memanggil fungsi yang dikirim pemanggil untuk setiap elemen; terapkanPadaSetiap() sendiri tidak tahu transformasi apa yang terjadi.
  3. Arrow function fn (int $harga): float => $harga * 1.11 adalah aturan PPN 11% yang dikirim sebagai argumen biasa, seperti mengoper angka atau string.

Tentu Anda sadar kita baru saja menulis ulang array_map() — dan memang itu intinya: array_map() adalah higher-order function yang sama, diproduksi pabrik. Dalam kode nyata pakailah array_map(); versi buatan ini hanya memperlihatkan isi mesinnya.

Callback di Berbagai Fungsi Bawaan #

PHP menyuntikkan gaya ini ke puluhan fungsi bawaan: array_map(), array_filter(), array_walk(), usort() beserta keluarganya, hingga preg_replace_callback(). Contoh klasiknya adalah pengurutan yang aturannya kita tentukan sendiri:

php
<?php

$peserta = [
    ['nama' => 'Budi', 'skor' => 88],
    ['nama' => 'Siti', 'skor' => 95],
    ['nama' => 'Andi', 'skor' => 71],
];

// Callback menentukan urutan; usort() yang mengatur array.
usort($peserta, fn (array $a, array $b): int => $b['skor'] <=> $a['skor']);

foreach ($peserta as $peringkat => $p) {
    echo ($peringkat + 1) . ". {$p['nama']} ({$p['skor']})\n";
}

Output yang Dihasilkan:

text
1. Siti (95)
2. Budi (88)
3. Andi (71)

Fungsi usort() tidak tahu apa arti “lebih besar” untuk data kita; fungsi itu hanya berjanji memanggil callback dengan dua elemen dan menaati tandanya — negatif, nol, atau positif — untuk menyusun ulang array. Callback kita memakai spaceship operator $b['skor'] <=> $a['skor'] yang sengaja dibalik urutannya agar hasil akhir menurun dari skor tertinggi. Satu catatan jujur mengikuti chapter Immutability : usort() termasuk kubu in-place, jadi $peserta memang berubah urutannya — namun callback-nya sendiri tetap murni, dan itulah bagian yang kita kendalikan.

Fungsi sebagai Hasil #

Bentuk kedua higher-order function justru mengembalikan fungsi. Manfaatnya: kita bisa membuat pabrik fungsi yang menghasilkan fungsi berperilaku mirip dengan konfigurasi berbeda:

php
<?php

// Higher-order function: MENGEMBALIKAN fungsi yang sudah dikonfigurasi.
function buatFormatterHarga(string $mataUang, string $simbol, int $desimal): callable
{
    return fn (float $nilai): string
        => $simbol . number_format($nilai, $desimal, ',', '.') . ' ' . $mataUang;
}

$formatIdr = buatFormatterHarga('IDR', 'Rp', 0);
$formatUsd = buatFormatterHarga('USD', '$', 2);

echo $formatIdr(1500000) . "\n";
echo $formatUsd(49.99) . "\n";

Output yang Dihasilkan:

text
Rp1.500.000 IDR
$49,99 USD

Sekali panggil, buatFormatterHarga('IDR', 'Rp', 0) menghasilkan fungsi siap pakai yang selalu memformat angka menjadi gaya rupiah; panggilan dengan argumen berbeda menghasilkan fungsi saudaranya bergaya dolar. Perhatikan pula hasilnya: dua fungsi lahir dari satu definisi, dan $formatIdr maupun $formatUsd adalah nilai biasa yang bisa disimpan di variabel, dimasukkan ke array, atau dioper ke fungsi lain. Mekanisme yang membuat fungsi hasil “mengingat” konfigurasinya adalah closure — kita bedah tuntas pada chapter berikutnya.

Partial Application #

Partial application adalah teknik menyediakan sebagian argumen sekarang, lalu menyisanya belakangan. PHP tidak punya sintaks bawaan untuk ini, tetapi higher-order function menutup celah tersebut:

php
<?php

function hargaDiskon(float $harga, float $persen): float
{
    return $harga * (100 - $persen) / 100;
}

// Partial application: membekukan argumen kedua di dalam closure.
function buatDiskon(float $persen): callable
{
    return fn (float $harga): float => hargaDiskon($harga, $persen);
}

$diskonMember = buatDiskon(10.0);
$diskonFlashSale = buatDiskon(25.0);

echo $diskonMember(200000) . "\n";
echo $diskonFlashSale(200000) . "\n";

Output yang Dihasilkan:

text
180000
150000

Fungsi buatDiskon() menerima persentase lalu mengembalikan fungsi satu-argumen yang “sudah tahu” persentasenya. Dari satu fungsi dua-argumen, kita memperoleh $diskonMember dan $diskonFlashSale — masing-masing siap diteruskan ke array_map() tanpa argumen tambahan. Pola ini sering dipakai untuk menyiapkan strategi berbeda dari mesin yang sama, dan menjadi bahan mentah penting saat kita membahas function composition nanti.

Combinator Secara Singkat #

Istilah combinator merujuk pada fungsi yang bekerja murni dengan mengombinasikan fungsi lain — tidak membaca keadaan luar, tidak menimbulkan efek. Dua contoh paling kecilnya:

php
<?php

// Combinator paling sederhana: identity — keluaran sama dengan masukan.
$identity = fn (mixed $nilai): mixed => $nilai;

// Combinator konstan: selalu mengabaikan masukan dan mengembalikan nilai tetap.
$selaluNol = fn (): int => 0;

var_dump($identity('PHP'));
var_dump($identity([1, 2, 3]));
var_dump($selaluNol());

Output yang Dihasilkan:

text
string(3) "PHP"
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
int(0)

Combinator identity tampak tidak berguna, tetapi justru sering dipakai sebagai callback bawaan ketika sebuah API menuntut fungsi padahal kita tidak ingin mengubah apa pun. Combinator sungguhan berikutnya — yang merangkai dua fungsi menjadi satu — bernama compose, dan perannya cukup besar sehingga kami menyimpannya untuk Chapter Function Composition .

Ringkasan #

Higher-order function memperlakukan fungsi sebagai warga kelas satu: masuk sebagai argumen callable, keluar sebagai nilai kembalian. Fungsi bawaan PHP memakai pola ini di mana-mana, usort() mencontohkan sisi argumennya, pabrik formatter mencontohkan sisi hasilnya, dan partial application menjembatani keduanya. Seluruh contoh pengembalian fungsi tadi bekerja karena satu mekanisme: closure yang menangkap lingkungan pembuatannya — itulah topik chapter berikutnya.

Referensi #