Topik ini membahas active pattern, yaitu fungsi yang didefinisikan sebagai pola sehingga dapat dipanggil dari posisi pola seperti arm match. Bentuknya ada dua, yaitu active pattern parsial yang mengembalikan satu hasil dan active pattern lengkap berparameter yang menutup seluruh kasus. Pembahasan bergerak berurutan dari bentuk parsial, bentuk lengkap berparameter, sampai penggabungan beberapa fungsi pola dalam satu arm. Setelah topik ini dibaca, penguraian kode status dan klasifikasi bilangan dapat ditulis sebagai pola yang dikenali compiler, bukan sebagai panggilan fungsi di dalam ekspresi arm.
Active pattern parsial#
Active pattern parsial ditulis dengan nama yang diapit kurung seperti (|Nama|_|), dan bentuk kurung itulah yang membuatnya dijuluki banana clips. Fungsinya mengembalikan tipe Option, yaitu tipe yang menyatakan ada atau tidaknya hasil sebagai Some dan None. Sifat Option inilah yang membuat pola parsial boleh gagal cocok, sehingga arm yang menggunakannya tetap memerlukan arm penutup untuk nilai yang tidak dikenali.
Berkas berikut mendefinisikan (|KodeStatus|_|) yang menguraikan kode status peminjaman koleksi perpustakaan berbentuk teks. Pada arm KodeStatus status, compiler memanggil fungsinya dengan nilai yang dicocokkan, lalu mengikat isi Some ke nama status apabila hasilnya ada. Pola ini dipanggil seperti pola lain, sehingga pembacaan match tetap seragam tanpa panggilan fungsi di dalam ekspresi arm. Nama active pattern ditulis dengan huruf kapital di awal mengikuti konvensi dokumentasi F#.
// Active pattern parsial mengubah fungsi pengurai menjadi pola
let (|KodeStatus|_|) (teks: string) =
if teks.StartsWith "D-" && teks.Length = 5 then Some "sedang dipinjam"
elif teks.StartsWith "R-" && teks.Length = 5 then Some "sedang dipesan"
else None
let terjemahkan teks =
match teks with
| KodeStatus status -> sprintf "%s berarti %s" teks status
| _ -> sprintf "%s bukan kode yang dikenal" teks
printfn "%s" (terjemahkan "D-001")
printfn "%s" (terjemahkan "R-010")
printfn "%s" (terjemahkan "X-999")D-001 berarti sedang dipinjam
R-010 berarti sedang dipesan
X-999 bukan kode yang dikenalActive pattern parsial memang berupa fungsi biasa yang mengembalikan Option, tetapi letak penggunaannya berbeda. Fungsi biasa dipanggil di dalam ekspresi arm setelah pola cocok, sedangkan active pattern dipanggil compiler pada proses pencocokan itu sendiri. Akibatnya, logika penguraian tidak menumpuk di ekspresi arm, dan nama polanya dapat digunakan ulang di banyak match tanpa mengulang pemeriksaan teksnya. Penguraian kode status memang lebih tepat ditulis sebagai fungsi biasa, karena pemeriksaan awalan dan panjang teks sulit dinyatakan sebagai pola biasa.
Active pattern lengkap berparameter#
Bentuk lengkap menuliskan lebih dari satu nama kasus di dalam kurung, misalnya (|Genap|Ganjil|), dan wajib mengembalikan salah satu kasus tersebut untuk setiap masukan. Karena himpunan kasusnya tertutup, compiler menghitungnya pada pemeriksaan kelengkapan, sehingga match yang menggunakannya tidak membutuhkan wildcard dan tetap bebas peringatan. Fungsi deskripsikan pada berkas berikut hanya menulis dua arm, dan pemeriksaan kelengkapannya berjalan seperti pola bawaan.
Active pattern juga dapat menerima parameter di posisi pola. Definisi (|Kelipatan|_|) menerima k selain nilai yang dicocokkan, sedangkan penggunaannya ditulis Kelipatan 5 dan seterusnya. Hasil Some () menyatakan kecocokan tanpa data tambahan, sehingga arm Kelipatan 5 tidak mengikat nama apa pun. Arm Kelipatan 15 sengaja ditempatkan paling atas karena pencocokan tetap berjalan dari atas ke bawah, sehingga bilangan 30 yang kelipatan 3 dan 5 sekaligus tertangkap di arm pertama.
// Active pattern lengkap membagi seluruh bilangan bulat menjadi dua kasus
let (|Genap|Ganjil|) angka =
if angka % 2 = 0 then Genap else Ganjil
// Active pattern parsial berparameter menguji keterbagian
let (|Kelipatan|_|) k angka =
if angka % k = 0 then Some () else None
let deskripsikan angka =
match angka with
| Genap -> sprintf "%d adalah bilangan genap" angka
| Ganjil -> sprintf "%d adalah bilangan ganjil" angka
let klasifikasikan angka =
match angka with
| Kelipatan 15 -> sprintf "%d kelipatan 3 dan 5 sekaligus" angka
| Kelipatan 5 -> sprintf "%d kelipatan 5" angka
| Kelipatan 3 -> sprintf "%d kelipatan 3" angka
| _ -> sprintf "%d bukan kelipatan 3 maupun 5" angka
printfn "%s" (deskripsikan 8)
printfn "%s" (deskripsikan 15)
for angka in [30; 25; 21; 7] do
printfn "%s" (klasifikasikan angka)8 adalah bilangan genap
15 adalah bilangan ganjil
30 kelipatan 3 dan 5 sekaligus
25 kelipatan 5
21 kelipatan 3
7 bukan kelipatan 3 maupun 5Jumlah kasus pada bentuk lengkap tidak terbatas dua. Misalnya (|Negatif|Nol|Positif|) membagi bilangan bertanda menjadi tiga kasus, dan compiler tetap menghitung semuanya untuk kelengkapan. Sebaliknya, apabila match hanya menggunakan sebagian kasusnya tanpa arm penutup, compiler kembali menerbitkan FS0025 karena kasus yang tersisa masih mungkin. Pemeriksaan yang sama itulah yang membuat active pattern lengkap terasa seperti pola bawaan.
Komposisi antar active pattern#
Beberapa active pattern dapat digabungkan dalam satu arm dengan pola AND yang ditulis &, dan tiap pola mengikat bagiannya sendiri. Arm StatusPinjam status & NomorUrut angka berarti kedua penguraian harus berhasil, lalu status dan angka digunakan di ekspresi arm yang sama. Guard tetap dapat menempel setelah pola gabungan tersebut, seperti arm pertama yang menyaring nomor genap.
Berkas berikut menguraikan kode peminjaman menjadi status dan nomor urut sekaligus. Nilai Z-100 gagal pada StatusPinjam karena huruf kodenya tidak dikenal, sedangkan D-0 gagal pada NomorUrut karena nomornya nol, sehingga keduanya jatuh ke arm penutup. Komposisi semacam ini memecah penguraian menjadi fungsi pola kecil yang saling disusun, alih-alih satu fungsi besar yang menguji segalanya.
// Dua active pattern parsial digabung dalam satu pola dengan &
let (|StatusPinjam|_|) (teks: string) =
if teks.StartsWith "D-" && teks.Length = 5 then Some "dipinjam"
elif teks.StartsWith "R-" && teks.Length = 5 then Some "dipesan"
else None
let (|NomorUrut|_|) (teks: string) =
match System.Int32.TryParse(teks.Substring 2) with
| true, angka when angka > 0 -> Some angka
| _ -> None
let periksa teks =
match teks with
| StatusPinjam status & NomorUrut angka when angka % 2 = 0 ->
sprintf "%s: status %s, nomor %d genap" teks status angka
| StatusPinjam status & NomorUrut angka ->
sprintf "%s: status %s, nomor %d ganjil" teks status angka
| _ -> sprintf "%s: kode tidak sah" teks
printfn "%s" (periksa "D-014")
printfn "%s" (periksa "R-007")
printfn "%s" (periksa "Z-100")
printfn "%s" (periksa "D-0")D-014: status dipinjam, nomor 14 genap
R-007: status dipesan, nomor 7 ganjil
Z-100: kode tidak sah
D-0: kode tidak sahKedua active pattern pada berkas tersebut tidak saling bergantung, sehingga masing-masing dapat diuji sendiri-sendiri sebelum digabung. Pola AND hanya menyatakan bahwa kedua kecocokan harus terjadi, sedangkan urutan arm tetap menentukan arm mana yang menang lebih dahulu. Definisi NomorUrut sendiri menggunakan guard di dalam pola TryParse, sehingga guard dan active pattern saling melengkapi di dua tingkat yang berbeda. Apabila kebutuhannya bertambah, active pattern baru cukup ditambahkan ke arm yang ada tanpa menyunting definisi yang sudah teruji.
Ringkasan#
- Active pattern adalah fungsi yang digunakan di posisi pola, dan bentuk parsialnya (|Nama|_|) mengembalikan Option sebagai penanda kecocokan.
- Active pattern lengkap seperti (|Genap|Ganjil|) menutup semua kasus, sehingga match-nya tidak memerlukan wildcard.
- Active pattern dapat berparameter, misalnya
Kelipatan 5, sehingga satu definisi melayani banyak pengujian. - Beberapa active pattern dapat digabung dalam satu arm dengan pola AND
&dan disempurnakan dengan guard.
Referensi#
- Active patterns — Microsoft Learn — Dokumentasi resmi active pattern parsial, lengkap, berparameter, dan penggunaannya.
- Pattern matching — Microsoft Learn — Daftar jenis pola F# yang mencakup pola active pattern.