Pola destructuring

Membongkar tuple, record, dan list langsung di dalam binding maupun pola match.

Daftar isi

Topik ini membahas pola destructuring, yaitu pola yang membongkar nilai gabungan menjadi bagian-bagiannya sekaligus mengikat tiap bagian ke nama baru. Pembongkaran dapat terjadi pada binding let, pada parameter fungsi, dan di dalam arm match. Tipe yang dibongkar meliputi tuple, record, dan list dari modul Pemodelan dengan tipe, ditambah discriminated union yang membawa record di dalam kasusnya.

Tuple pada binding dan parameter#

Binding let menerima pola di sisi kirinya, bukan hanya satu nama. Pola tuple seperti let (nama, usia) = ... membongkar satu pasangan nilai menjadi dua nama sekaligus, dan compiler memeriksa jumlah serta bentuk elemennya. Bagian yang tidak digunakan dilepas dengan wildcard _ agar tidak ada nama yang menganggur.

Destructuring pada let mengevaluasi ekspresi di sisi kanan satu kali lalu mengikat seluruh bagiannya sekaligus, sehingga tidak ada akses elemen berulang. Bentuk nilainya juga sudah dijamin oleh tipenya, karena tuple dua elemen tidak mungkin berubah menjadi tiga elemen. Karena itulah pola pada let tepat untuk data yang bentuknya pasti, sedangkan data yang bervariasi ditangani oleh match dengan arm penutup.

Parameter fungsi juga merupakan pola, bukan sekadar nama. Fungsi perkenalkan pada berkas berikut menerima satu argumen berbentuk tuple dan langsung membongkarnya menjadi nama dan kota di dalam daftar parameternya. Pemanggil tetap mengirim satu tuple utuh, sehingga bentuk pemanggilannya sama seperti pemanggilan fungsi tuple biasa. Binding ketiga pada berkas tersebut mencontohkan wildcard di tengah tuple, yaitu dua bagian pertama dilepas dan hanya tahun yang diikat.

fsharp bongkar-tuple.fsx
// Membongkar tuple pada binding let
let (nama, usia) = ("Ali", 19)
printfn "%s berusia %d tahun" nama usia

// Wildcard melepas bagian tuple yang tidak dipakai
let (_, _, tahun) = ("Kamus Mini", "Bahasa", 2020)
printfn "terbit pada tahun %d" tahun

// Parameter fungsi juga berupa pola tuple
let perkenalkan (nama, kota) =
    printfn "%s tinggal di %s" nama kota

perkenalkan ("Budi", "Bandung")
perkenalkan ("Citra", "Surabaya")
text Keluaran
Ali berusia 19 tahun
terbit pada tahun 2020
Budi tinggal di Bandung
Citra tinggal di Surabaya

Record pada binding dan parameter fungsi#

Pola record ditulis dengan kurung kurawal dan memetakan bidang ke nama baru, misalnya let { Judul = judul } = buku. Bentuknya boleh parsial, yaitu hanya bidang yang dibutuhkan yang disebut, sedangkan bidang lainnya dilepas tanpa peringatan karena kumpulan bidang record bersifat tetap dan sudah diketahui compiler. Sama seperti tuple, pola record dapat dipasang pada binding maupun pada parameter fungsi.

Berkas berikut membongkar record Buku dengan tiga bentuk, yaitu pola lengkap pada binding, pola parsial pada binding, dan pola pada parameter pertama fungsi umurBuku. Bentuk terakhir membuat badan fungsi langsung menggunakan nama tahun tanpa menulis akses bidang seperti buku.Tahun, dan pemanggilan umurBuku buku 2026 mengirim record serta tahun acuan sebagai dua argumen tersendiri.

fsharp bongkar-record.fsx
type Buku = { Judul: string; Tahun: int }
let buku = { Judul = "Kamus Mini"; Tahun = 2020 }

// Pola record pada binding let
let { Judul = judul; Tahun = tahun } = buku
printfn "%s terbit %d" judul tahun

// Cukup bidang yang dibutuhkan saja
let { Judul = judulLain } = buku
printfn "judul saja: %s" judulLain

// Pola record pada parameter fungsi
let umurBuku { Tahun = tahun } tahunIni = tahunIni - tahun
printfn "umur buku %d tahun" (umurBuku buku 2026)
text Keluaran
Kamus Mini terbit 2020
judul saja: Kamus Mini
umur buku 6 tahun

List sebagai pola kons#

List F# tersusun sebagai rantai kepala dan ekor, dan polanya mengikuti struktur itu. Pola [] mencocokkan list kosong, sedangkan pola kons kepala :: ekor memisahkan elemen pertama dari sisa listnya. Kedua pola ini saling melengkapi dan menjadi dasar penulisan fungsi list secara rekursif, seperti jumlahkan pada berkas berikut yang menjumlahkan seluruh elemennya.

Wildcard juga berlaku di dalam pola list. Pola kepala :: _ mengikat elemen pertama dan melepas sisanya, sehingga tepat untuk fungsi kepalaPertama yang tidak bergantung pada panjang listnya. Sementara itu, pola panjang tetap seperti [pertama; kedua] hanya cocok apabila list memuat tepat dua elemen, sehingga arm penutupnya tetap diperlukan untuk list yang lebih pendek maupun lebih panjang.

fsharp bongkar-list.fsx
// Pola kons memisahkan kepala dan ekor list
let rec jumlahkan daftar =
    match daftar with
    | [] -> 0
    | kepala :: ekor -> kepala + jumlahkan ekor

printfn "jumlah: %d" (jumlahkan [10; 20; 30])
printfn "jumlah: %d" (jumlahkan [])

// Wildcard melepas ekor yang tidak dipakai
let kepalaPertama daftar =
    match daftar with
    | kepala :: _ -> sprintf "kepala: %s" kepala
    | [] -> "list kosong"

printfn "%s" (kepalaPertama ["Ali"; "Budi"; "Citra"])
printfn "%s" (kepalaPertama [])

// Pola panjang tetap mencocokkan list dengan jumlah elemen pasti
let duaPertama daftar =
    match daftar with
    | [pertama; kedua] -> sprintf "dua pertama: %s dan %s" pertama kedua
    | _ -> "kurang dari dua anggota"

printfn "%s" (duaPertama ["Ali"; "Budi"])
printfn "%s" (duaPertama ["Dewi"])
text Keluaran
jumlah: 60
jumlah: 0
kepala: Ali
list kosong
dua pertama: Ali dan Budi
kurang dari dua anggota

Record di dalam pola discriminated union#

Pola dapat ditulis bersarang, yaitu pola di dalam pola lain. Penggunaannya paling lazim pada kasus discriminated union yang membawa data, misalnya record. Arm Aktif { Nama = nama; TahunMasuk = tahun } mencocokkan kasus Aktif sekaligus membongkar record yang dibawanya dalam satu pola, sehingga nama dan tahun masuk tersedia langsung di ekspresi arm.

Tanpa pola bersarang, proses yang sama membutuhkan dua langkah, yaitu mengikat record ke satu nama terlebih dahulu lalu membaca bidangnya satu per satu. Pola parsial juga sah di sini, seperti arm Nonaktif { Nama = nama } yang hanya membutuhkan bidang Nama dan melepas TahunMasuk. Kasus tanpa data seperti LayananTutup dicocokkan sebagai pola biasa tanpa pembongkaran.

fsharp anggota-du.fsx
// Record dibongkar di dalam pola discriminated union
type Anggota = { Nama: string; TahunMasuk: int }

type StatusKeanggotaan =
    | Aktif of Anggota
    | Nonaktif of Anggota
    | LayananTutup

let deskripsikan status =
    match status with
    | Aktif { Nama = nama; TahunMasuk = tahun } ->
        sprintf "%s aktif sejak %d" nama tahun
    | Nonaktif { Nama = nama } ->
        sprintf "%s nonaktif, perlu aktivasi ulang" nama
    | LayananTutup -> "layanan tutup sementara"

let ali = { Nama = "Ali"; TahunMasuk = 2023 }
let budi = { Nama = "Budi"; TahunMasuk = 2024 }

printfn "%s" (deskripsikan (Aktif ali))
printfn "%s" (deskripsikan (Nonaktif budi))
printfn "%s" (deskripsikan LayananTutup)
text Keluaran
Ali aktif sejak 2023
Budi nonaktif, perlu aktivasi ulang
layanan tutup sementara

Kesalahan umum#

Kesalahan pertama yang lazim adalah ketidakcocokan jumlah elemen tuple. Binding let (a, b) = (1, 2, 3) ditolak compiler dengan error FS0001 yang menyatakan tuple sepanjang dua diberi nilai sepanjang tiga. Perbaikannya adalah menyamakan jumlah nama pada pola dengan jumlah elemen nilai yang diikat.

Kesalahan kedua adalah memanggil fungsi list secara rekursif tanpa kata kunci rec. Definisi let jumlahkan daftar = yang di dalam badannya memanggil jumlahkan lagi ditolak compiler dengan error FS0039 karena nama itu belum terdefinisi pada saat badan fungsinya diperiksa. Cukup tambahkan rec sehingga menjadi let rec jumlahkan daftar = seperti pada bongkar-list.fsx.

Pesan compiler untuk kedua kasus tersebut menyebut letak baris dan kolomnya, sehingga penggunaan pola yang keliru mudah dilacak. Perbaiki pesan peringatan dan error sejak muncul karena pencocokan pola adalah bagian yang diperiksa ketat oleh compiler, dan ketelitian di sini mencegah MatchFailureException pada waktu berjalan.

Ringkasan#

  • Pola destructuring membongkar tuple, record, dan list menjadi nama-nama bagiannya, baik pada let, parameter fungsi, maupun arm match.
  • Wildcard _ melepas bagian yang tidak digunakan, sehingga tidak ada nama yang menganggur.
  • Pola record boleh parsial karena bidangnya tetap dan diketahui compiler, sedangkan pola list pada let menuntut bentuk yang pasti.
  • Pola bersarang membongkar data berlapis, misalnya record di dalam kasus discriminated union, dalam satu arm.

Referensi#