Operator pin ^

Pengikatan ulang dan pencocokan pola, dengan ^ untuk batasan nilai yang sudah ada.

Daftar isi

Operator pin ^ mengunci satu bagian pola pada nilai variabel yang sudah ada, sehingga pencocokan berperan sebagai perbandingan dan bukan pengikatan baru. Topik ini membedakan pengikatan ulang (rebinding) yang dilakukan = biasa dengan pencocokan yang dipaksa oleh pin, lalu menunjukkan penggunaannya pada pola tuple, map, case, dan klausa fungsi anonim. Setelah topik ini selesai, batasan terhadap nilai yang sudah terikat dapat dinyatakan secara eksplisit di dalam pola.

Pengikatan ulang dengan =#

Variabel pada Elixir boleh diikat ke nilai baru kapan saja. Ekspresi x = 1 mengikat x ke 1, dan x = 2 sesudahnya mengganti ikatan itu menjadi 2 tanpa peringatan apa pun. Peristiwa ini disebut pengikatan ulang (rebinding), yaitu pemberian ikatan baru pada nama variabel yang sama. Data lama tidak tersentuh karena data pada Elixir imutabel. Yang berganti hanyalah nilai yang dirujuk oleh nama tersebut.

elixir
x = 1
IO.puts(x)

x = 2
IO.puts(x)
text Keluaran
1
2

Pengikatan ulang paling sering muncul dalam bentuk kecil, misalnya memperbarui hasil sementara di antara dua langkah perhitungan. Nama yang digunakan ulang pada keadaan semacam ini masih menggambarkan satu makna, sehingga keterbacaannya terjaga. Keadaan yang lain adalah penggunaan satu nama untuk dua makna, karena pembaca kode akan mengira kedua nilai itu satu hal, dan pada keadaan demikian penamaan baru perlu dipertimbangkan.

Kelonggaran ini praktis, tetapi menimbulkan satu risiko: sebuah = yang dimaksudkan sebagai perbandingan dapat diam-diam menjadi penggantian nilai. Bahasa yang variabelnya imutabel biasanya menuntut sintaks khusus untuk keadaan ini, dan Elixir menjawab kebutuhan yang sama dengan operator pin.

Operator pin ^#

Pin ^ diletakkan di depan nama variabel di dalam pola untuk menyatakan “gunakan nilai yang sudah terikat, jangan ikat ulang”. Ekspresi ^x = 1 hanya berhasil apabila nilai x saat itu memang 1. Apabila nilainya berbeda, pencocokan gagal dan MatchError dilempar, sebagaimana kegagalan pencocokan pola pada umumnya.

elixir
x = 1
^x = 1
IO.puts("pola ^x cocok dengan nilai 1")

try do
  ^x = 2
rescue
  e in MatchError -> IO.puts("MatchError: #{Exception.message(e)}")
end

IO.puts(x)
text Keluaran
pola ^x cocok dengan nilai 1
MatchError: no match of right hand side value:

    2

1

Baris terakhir keluaran memperlihatkan bahwa x tetap bernilai 1 setelah pencocokan gagal. Pin tidak mengubah apa pun. Ia hanya menuntut kesamaan. Dengan demikian ^x = 2 dibaca sebagai asersi “x harus bernilai 2”, sedangkan x = 2 tanpa pin adalah perintah “jadikan x bernilai 2”. Satu karakter pin ini memisahkan dua maksud yang sangat berbeda.

Pin juga berbeda dari pengikatan biasa dalam hal kegagalan. Pengikatan ulang tidak pernah gagal karena ia tidak membandingkan apa pun, sedangkan pencocokan dengan pin dapat gagal dan kegagalannya adalah informasi yang berharga. Gaya pemrograman Elixir memanfaatkan sifat ini dengan meletakkan asersi di jalur data, sehingga dugaan yang keliru terlihat di titik kemunculannya, bukan jauh setelahnya.

Pin dalam pola data dan klausa#

Pin bekerja pada setiap posisi di dalam pola, bukan hanya pada pola sederhana. Nama variabel pada setiap klausa case dan fn juga memiliki lingkup sendiri, sehingga nama yang sama tanpa pin di dalam pola tidak membandingkan, melainkan mengikat variabel baru yang menyembunyikan nama di lingkup luarnya. Pin menyatakan maksud sebaliknya, yaitu membandingkan dengan nilai lingkup luar. Pada pola tuple, {^batas_bawah, atas} = {0, 100} menuntut elemen pertama sama dengan nilai batas_bawah yang sudah ada, sedangkan atas tetap diikat baru. Pada pola map, kunci yang sudah terikat dapat dikenai pin sehingga %{^kunci => nilai} mencari kunci tersebut secara dinamis.

elixir
batas_bawah = 0
{^batas_bawah, atas} = {0, 100}
IO.inspect(atas, label: "atas")

kunci = :bahasa
%{^kunci => terjemahan} = %{bahasa: "Elixir"}
IO.puts(terjemahan)
text Keluaran
atas: 100
Elixir

Kombinasi ini berguna ketika pembanding baru diketahui saat eksekusi berlangsung, misalnya pengenal yang sedang dicari. Klausa case dan fungsi anonim menggunakan pin dengan cara yang sama, sehingga satu nilai dari luar dapat dijadikan pembanding pada setiap klausanya.

elixir
target = 42
hasil = case 42 do
  ^target -> "menemukan target"
  _ -> "bukan target"
end
IO.puts(hasil)

batas = 100
cek = fn
  ^batas -> "tepat batas"
  _ -> "di luar batas"
end
IO.puts(cek.(100))
IO.puts(cek.(90))
text Keluaran
menemukan target
tepat batas
di luar batas

Pada klausa cek, nilai batas dikenai pin sehingga klausanya hanya menerima argumen yang sama dengan 100. Argumen lain dialihkan ke klausa _. Tanpa pin, klausa pertama justru akan mengikat ulang batas ke argumen apa pun yang masuk, dan pembedaan itu hilang.

Pin juga membuat pola dapat menuntut dua bagian nilai yang sama. Setelah x terikat, pola {x, ^x} hanya cocok dengan pasangan yang kedua elemennya sama dengan x, dan bentuk serupa berlaku pada map maupun list. Contoh berikut memperlihatkan pasangan yang cocok dan pasangan yang ditolak.

elixir
x = 1
{x, ^x} = {1, 1}
IO.inspect(x, label: "x")

y = 1
try do
  {_, ^y} = {1, 2}
rescue
  e in MatchError -> IO.puts("MatchError: #{Exception.message(e)}")
end
text Keluaran
x: 1
MatchError: no match of right hand side value:

    {1, 2}

Kesalahan umum#

  • Lupa pin ketika maksudnya membandingkan. Penulisan x = 2 setelah x = 1 tidak memunculkan error apa pun karena yang terjadi adalah pengikatan ulang. Perbaikannya, tulis ^x = 2 agar kegagalan terlihat sebagai MatchError.
  • Menggunakan pin pada kepala fungsi def. Lingkup modul tidak memiliki variabel luar yang dapat dikenai pin, sehingga def f(^x) gagal dikompilasi. Perbaikannya, bawa nilai pembanding sebagai argumen biasa atau nyatakan syaratnya dengan guard when.
  • Mengira pin mengubah nilai. Pin tidak pernah mengatur atau menetapkan apa pun. Ia hanya menuntut kesamaan. Penggantian nilai tetap menjadi pekerjaan = tanpa pin.
  • Menyertakan pin pada variabel yang memang akan diikat baru. Pin hanya bermakna untuk nilai yang sudah ada, sehingga ^baru = 1 tanpa ikatan baru sebelumnya gagal dikompilasi. Perbaikannya, gunakan nama polos ketika maksudnya mengikat, dan pin ketika maksudnya membandingkan.

Ringkasan#

  • = mengikat ulang variabel tanpa peringatan, sedangkan ^x menuntut nilai yang sudah ada.
  • ^x = 2 gagal dengan MatchError apabila nilai x bukan 2, dan pin tidak pernah mengubah nilai x.
  • Pin sah pada pola tuple, pola map, klausa case, dan klausa fungsi anonim.

Referensi#