Bentuk pola

Pola literal, variabel, tuple, barisan, dan alternatif beserta urutan pencocokannya.

Daftar isi

Setiap cabang case pada ekspresi match memuat satu pola, dan bentuk pola itulah yang menentukan nilai seperti apa yang dianggap cocok beserta ikatan yang dihasilkannya. Topik ini menguraikan pola literal, pola konstanta, pola variabel, pola tuple, pola barisan pada List, serta alternatif | yang menggabungkan beberapa pola dalam satu cabang. Cara compiler menentukan urutan pencocokan beserta akibat letak pola yang keliru dibahas pada bagian akhir. Setelah membacanya, setiap cabang case dapat ditulis dengan tahu persis pola mana yang digunakan.

Pola literal dan pola konstanta#

Pola literal adalah pola berupa nilai yang ditulis langsung, seperti 0, "S", atau true, dan pencocokannya membandingkan nilai yang diuji dengan nilai tertulis itu menggunakan kesetaraan. Pola konstanta berbentuk serupa, tetapi nilainya dirujuk melalui pengenal berhuruf kapital yang menunjuk ke val yang sudah ada, sehingga nilai yang sama dapat digunakan pada beberapa match tanpa ditulis ulang. Keduanya menghasilkan pencocokan yang paling langsung dan mudah dibaca. Program berikut menerjemahkan kode pintasan satu huruf ke dalam arti masing-masing.

scala literal.scala
// pola konstanta berhuruf besar merujuk val, pola variabel kecil mengikat
val KodeSelesai = "S"
val KodeBatal = "B"

@main def sandi(): Unit =
  val masukan: List[String] = List("S", "B", "X")

  for kode <- masukan do
    val arti =
      kode match
        case KodeSelesai => "tugas selesai"
        case KodeBatal   => "tugas dibatalkan"
        case lain        => s"kode tidak dikenal: $lain"

    println(s"$kode -> $arti")
bash
# menjalankan program literal.scala
scala-cli run literal.scala
text Keluaran
S -> tugas selesai
B -> tugas dibatalkan
X -> kode tidak dikenal: X

Cabang pertama dan kedua menggunakan pola konstanta KodeSelesai dan KodeBatal yang menunjuk ke val berhuruf kapital pada berkas yang sama, sedangkan cabang ketiga menggunakan pola variabel lain untuk menampung kode yang tidak dikenal. Pencocokan pada dua cabang pertama berarti membandingkan kode dengan isi kedua konstanta itu, bukan mengikat ulang. Huruf kapital di sini bukan sekadar gaya penulisan, melainkan sinyal kepada compiler bahwa nilai pengenal tersebut harus dirujuk. Perlakuan berbeda terhadap huruf kecil dan huruf besar dibedah pada bagian berikutnya.

Pola variabel dan konstanta berhuruf kapital#

Compiler menentukan arti sebuah pengenal pada pola berdasarkan huruf pertamanya. Pengenal yang berawal huruf kecil selalu dibaca sebagai pola variabel, yaitu pola yang cocok dengan nilai apa pun sekaligus mengikatnya, meskipun ada val dengan nama yang sama di sekitarnya. Pengenal berawal huruf kapital dibaca sebagai rujukan konstanta, yaitu pencocokan terhadap nilai yang dirujuknya. Perbedaan ini menimbulkan jebakan ketika konstanta ditulis dengan huruf kecil, sebagaimana ditunjukkan program berikut yang menjalankan kedua bentuk tersebut berdampingan.

scala variabel.scala
// pengenal kecil pada pola selalu mengikat nilai, bukan membandingkannya
val batas = 75
val Batas = 75

@main def perbandingan(): Unit =
  val ujian: List[Int] = List(90, 75, 60)

  println("pola variabel, pengenal kecil")
  for n <- ujian do
    val status =
      n match
        case batas => "batas"
        case _     => "bukan batas"

    println(s"$n -> $status")

  println("pola konstanta, pengenal besar")
  for n <- ujian do
    val status =
      n match
        case Batas => "tepat di batas"
        case _     => "bukan batas"

    println(s"$n -> $status")
bash
# menjalankan program variabel.scala
scala-cli run variabel.scala
text Keluaran
[warn] ./variabel.scala:13:14
[warn] Unreachable case
[warn]         case _     => "bukan batas"
[warn]              ^
pola variabel, pengenal kecil
90 -> batas
75 -> batas
60 -> batas
pola konstanta, pengenal besar
90 -> bukan batas
75 -> tepat di batas
60 -> bukan batas

Keluaran di atas diawali peringatan compiler sebelum keluaran program. Peringatan Unreachable case menunjuk ke case _ karena case batas cocok dengan nilai apa pun, sehingga cabang di bawahnya tidak mungkin terjangkau. Akibatnya terlihat pada blok pertama: ketiga nilai ujian tetap dicetak sebagai batas karena pola itu mengikat nilai, bukan membandingkan. Blok kedua menunjukkan perbaikannya, yaitu konstanta Batas berhuruf kapital sehingga hanya 75 yang dicetak sebagai tepat di batas.

Pola tuple#

Pola tuple mencocokkan pasangan atau gabungan nilai lain dengan membongkarnya menjadi pola-pola penyusunnya. Pola (0, 0) hanya cocok untuk pasangan yang kedua elemennya bernilai nol, sedangkan pola (x, 0) cocok untuk pasangan apa pun dengan elemen kedua bernilai nol dan mengikat elemen pertamanya ke x. Posisi penulisan menentukan makna: elemen pertama dipasangkan dengan pola pertama, elemen kedua dengan pola kedua. Program berikut mengelompokkan titik-titik pada bidang berdasarkan posisinya.

scala tuple.scala
// pola tuple mendestrukturisasi pasangan menjadi pengenal-polanya
@main def posisi(): Unit =
  val titik: List[(Int, Int)] = List((0, 0), (4, 0), (0, 3), (2, 5))

  for t <- titik do
    val keterangan =
      t match
        case (0, 0) => "titik asal"
        case (x, 0) => s"di sumbu mendatar, x = $x"
        case (0, y) => s"di sumbu tegak, y = $y"
        case (x, y) => s"di kuadran dalam, ($x, $y)"

    println(s"$t -> $keterangan")
bash
# menjalankan program tuple.scala
scala-cli run tuple.scala
text Keluaran
(0,0) -> titik asal
(4,0) -> di sumbu mendatar, x = 4
(0,3) -> di sumbu tegak, y = 3
(2,5) -> di kuadran dalam, (2, 5)

Urutan cabang menunjukkan pola yang lebih spesifik diletakkan lebih dulu, sehingga (0, 0) ditangani sebelum (x, 0) dan (0, y). Elemen yang tidak diperlukan dapat diganti dengan tanda _, misalnya pola (0, _) yang hanya memeriksa elemen pertama. Pola tuple berlaku untuk tuple bertipe apa pun, dan jumlah pola penyusunnya wajib sama dengan jumlah elemen tuple yang diuji, sehingga pola (x, y) tidak akan pernah cocok dengan tuple tiga elemen. Tuple juga dapat bersarang, sehingga satu pola dapat membongkar pasangan yang isinya pasangan lagi.

scala Contoh: pola tuple bersarang
case ((0, 0), (x, y)) => s"berangkat dari titik asal menuju ($x, $y)"

Pola barisan pada List#

Pola barisan (sequence pattern) mencocokkan koleksi berurutan seperti List menurut jumlah dan susunan elemennya. Bentuk yang paling langsung adalah pola berupa daftar literal, misalnya List(satu) untuk daftar dengan tepat satu elemen dan List(a, b) untuk dua elemen, yang mengikat setiap elemen ke pengenalnya. Untuk daftar dengan panjang berapa pun, pola infiks kepala :: ekor memisahkan elemen pertama dari sisanya, dengan :: dibaca sebagai penghubung antara kepala dan ekor daftar. Pola Nil menangkap daftar kosong. Program berikut membedah empat daftar dengan panjang yang berbeda.

scala barisan.scala
// pola barisan mencocokkan daftar menurut jumlah dan susunan elemennya
@main def bedahDaftar(): Unit =
  val kumpulan: List[List[Int]] = List(List(), List(7), List(3, 9), List(1, 2, 5, 8))

  for d <- kumpulan do
    val keterangan =
      d match
        case Nil            => "kosong"
        case List(satu)     => s"tepat satu elemen: $satu"
        case List(a, b)     => s"tepat dua elemen: $a dan $b"
        case kepala :: ekor => s"berawalan $kepala, ekor ${ekor.length} elemen"

    println(s"$d -> $keterangan")
bash
# menjalankan program barisan.scala
scala-cli run barisan.scala
text Keluaran
List() -> kosong
List(7) -> tepat satu elemen: 7
List(3, 9) -> tepat dua elemen: 3 dan 9
List(1, 2, 5, 8) -> berawalan 1, ekor 3 elemen

Daftar empat elemen tidak terjangkau oleh ketiga pola pertama karena jumlah elemennya berbeda, sehingga jatuh ke pola kepala :: ekor. Pola ini cocok dengan daftar apa pun yang tidak kosong, mengikat 1 ke kepala dan daftar List(2, 5, 8) ke ekor, dan itulah bentuk yang paling sering digunakan untuk menelusuri daftar secara rekursif. Ekor daftar juga dapat diikat sekaligus dengan pola barisan yang berakhir dengan tanda *.

scala Contoh: pola barisan dengan ekor terikat
case List(pertama, sisanya*) => s"pertama $pertama, sisanya ${sisanya.length} elemen"

Pada pola di atas, sisanya* menampung nol elemen atau lebih setelah pertama, dan ikatannya bertipe Seq. Bentuk dengan tanda * inilah penulisan Scala 3 untuk pola barisan variabel yang pada Scala 2 ditulis sisanya @ _*.

Alternatif dan urutan pencocokan#

Tanda | menggabungkan beberapa pola menjadi satu alternatif, dan cabangnya cocok apabila salah satu pola dalam alternatif itu cocok. Program berikut memilah nama hari menggunakan dua alternatif, sehingga lima hari kerja tidak perlu ditulis sebagai lima cabang terpisah.

scala alternatif.scala
// tanda | menggabungkan beberapa pola pada satu case yang sama
@main def sifatHari(): Unit =
  val urutanHari: List[String] = List("Sabtu", "Minggu", "Senin", "Jumat", "Kilat")

  for hari <- urutanHari do
    val sifat =
      hari match
        case "Sabtu" | "Minggu" => "akhir pekan"
        case "Senin" | "Selasa" | "Rabu" | "Kamis" | "Jumat" => "hari kerja"
        case lain => s"tidak dikenal: $lain"

    println(s"$hari -> $sifat")
bash
# menjalankan program alternatif.scala
scala-cli run alternatif.scala
text Keluaran
Sabtu -> akhir pekan
Minggu -> akhir pekan
Senin -> hari kerja
Jumat -> hari kerja
Kilat -> tidak dikenal: Kilat

Alternatif tetap merupakan satu cabang, sehingga hasilnya hanya satu dan tidak boleh mengikat pengenal yang hanya ada pada sebagian polanya. Compiler menolak pola seperti case x | "Kilat" dengan pesan error Illegal variable x in pattern alternative, karena x hanya terikat pada satu sisi alternatif.

Pencocokan tetap berjalan dari atas ke bawah, sehingga cabang pertama yang cocok menang dan cabang-cabang di bawahnya tidak diuji lagi. Aturan praktisnya adalah menempatkan pola paling spesifik di atas dan pola paling umum di bawah, karena pola variabel yang diletakkan lebih dulu menyerap semua nilai dan membuat cabang sesudahnya sia-sia.

scala Contoh: cabang yang tidak terjangkau karena urutan
val keterangan =
  n match
    case lain => "selalu cocok"
    case 0    => "tidak pernah terjangkau"

Compiler menandai case 0 dengan peringatan Unreachable case, karena case lain di atasnya sudah menangkap nilai apa pun. Aturan letak yang sama berlaku untuk pola lain: pola yang lebih sempit, seperti List(a, b), ditulis sebelum pola yang lebih luas, seperti kepala :: ekor. Pola yang mendestrukturisasi objek case class menggunakan mekanisme pencocokan yang sama dan diuraikan pada topik destrukturisasi data.

Ringkasan#

  • Pola literal dan pola konstanta membandingkan nilai secara langsung, dengan konstanta ditulis berhuruf kapital sebagai rujukan val.
  • Pola variabel berhuruf kecil cocok dengan nilai apa pun dan mengikatnya, sehingga konstanta yang ditulis dengan huruf kecil membuat pencocokan gagal tanpa terlihat.
  • Pola tuple membongkar pasangan menurut posisi, pola barisan mencocokkan List menurut jumlah dan susunannya dengan :: dan *, sedangkan | menggabungkan beberapa pola dalam satu cabang.
  • Pencocokan berjalan dari atas ke bawah dan berhenti pada cabang pertama yang cocok, sehingga pola spesifik ditulis lebih dulu dan pola umum ditutup di bagian bawah.

Referensi#